Reflection is a handy mechanism in .NET that enables you to obtain class information, get and set properties, and invoke methods entirely at run-time. Reflection can also provide information about the object and method that called a particular method. This can be useful for debug and trace purposes.
The following CallingMethod class wraps up this capability in a handy C# object:
using System; using System.Diagnostics; using System.IO; using System.Reflection; namespace DevTopics { /// <summary> /// Contains information about the calling method. /// </summary> public class CallingMethod { #region CONSTRUCTORS #region DEFAULT /// <summary> /// Gets the calling method. /// </summary> public CallingMethod() : this( null ) { } #endregion DEFAULT #region IGNORE /// <summary> /// Gets the calling method, ignoring calls from the specified type. /// </summary> /// <param name="ignoreType">All calls made from this type will be ignored. /// Use this when wrapping this class in another class. OK if null.</param> public CallingMethod( Type ignoreType ) { this.m_IgnoreType = ignoreType; this.Initialize(); } #endregion IGNORE #endregion CONSTRUCTORS #region FILE #region NAME private string m_FileName; /// <summary> /// Gets the name of the file that contained the method. /// </summary> public string FileName { get { return this.m_FileName; } } #endregion NAME #region PATH private string m_FilePath; /// <summary> /// Gets the path of the file that contained the method. /// </summary> public string FilePath { get { return this.m_FilePath; } } #endregion PATH #endregion FILE #region IGNORE private Type m_IgnoreType; /// <summary> /// Gets the type that will be ignored. /// </summary> public Type IgnoreType { get { return this.m_IgnoreType; } } #endregion IGNORE #region INITIALIZE /// <summary> /// Initializes the calling method information. /// </summary> private void Initialize() { #region METHOD BASE MethodBase method = null; string ignoreName = this.m_IgnoreType == null ? null : this.m_IgnoreType.Name; #endregion METHOD BASE #region STACK TRACE StackFrame stackFrame = null; StackTrace stackTrace = new StackTrace( true ); for (int i = 0; i < stackTrace.FrameCount; i++) { StackFrame sf = stackTrace.GetFrame( i ); method = sf.GetMethod(); string typeName = method.ReflectedType.Name; if (String.Compare( typeName, "CallingMethod" ) != 0 && (ignoreName == null || String.Compare( typeName, ignoreName ) != 0)) { stackFrame = sf; break; } } #endregion STACK TRACE #region METHOD method = stackFrame.GetMethod(); this.m_Method = method; string methodString = method.ToString(); #endregion METHOD #region SIGNATURE string returnName = null; string methodSignature = methodString; int splitIndex = methodString.IndexOf( ' ' ); if (splitIndex > 0) { returnName = methodString.Substring( 0, splitIndex ); methodSignature = methodString.Substring( splitIndex + 1, methodString.Length - splitIndex - 1 ); } this.m_ReturnName = returnName; this.m_MethodSignature = methodSignature; #endregion SIGNATURE #region TYPE this.m_Type = method.ReflectedType; this.m_TypeName = this.m_Type.Name; this.m_TypeNameFull = this.m_Type.FullName; #endregion TYPE #region METHOD this.m_MethodName = method.Name; this.m_MethodNameFull = String.Concat( this.m_TypeNameFull, ".", this.m_MethodName ); #endregion METHOD #region FILE this.m_LineNumber = stackFrame.GetFileLineNumber(); string fileLine = null; this.m_FilePath = stackFrame.GetFileName(); if (!String.IsNullOrEmpty( this.m_FilePath )) { this.m_FileName = Path.GetFileName( this.m_FilePath ); fileLine = String.Format( "File={0}, Line={1}", this.m_FileName, this.m_LineNumber ); } #endregion FILE #region FULL SIGNATURE this.m_MethodSignatureFull = String.Format( "{0} {1}.{2}", returnName, this.m_TypeNameFull, this.m_MethodSignature ); this.m_Text = String.Format( "{0} [{1}]", this.m_MethodSignatureFull, fileLine ); #endregion FULL SIGNATURE } #endregion INITIALIZE #region LINE NUMBER private int m_LineNumber; /// <summary> /// Gets the line number in the file that called the method. /// </summary> public int LineNumber { get { return this.m_LineNumber; } } #endregion LINE NUMBER #region METHOD #region NAME #region FULL private string m_MethodNameFull; /// <summary> /// Gets the full name of this method, with namespace. /// </summary> public string MethodNameFull { get { return this.m_MethodNameFull; } } #endregion FULL #region METHOD private MethodBase m_Method; /// <summary> /// Gets the calling method. /// </summary> public MethodBase Method { get { return this.m_Method; } } #endregion METHOD #region NORMAL private string m_MethodName; /// <summary> /// Gets the name of this method. /// </summary> public string MethodName { get { return this.m_MethodName; } } #endregion NORMAL #endregion NAME #region SIGNATURE #region FULL private string m_MethodSignatureFull; /// <summary> /// Gets the complete method signature /// with return type, full method name, and arguments. /// </summary> public string MethodSignatureFull { get { return this.m_MethodSignatureFull; } } #endregion FULL #region NORMAL private string m_MethodSignature; /// <summary> /// Gets the method name and arguments. /// </summary> public string MethodSignature { get { return this.m_MethodSignature; } } #endregion NORMAL #endregion SIGNATURE #endregion METHOD #region NAMESPACE /// <summary> /// Gets the namespace containing the object containing this method. /// </summary> public string Namespace { get { Type type = this.Type; return type == null ? null : type.Namespace; } } #endregion NAMESPACE #region RETURN private string m_ReturnName; /// <summary> /// Gets the name of the return type. /// </summary> public string ReturnName { get { return this.m_ReturnName; } } #endregion RETURN #region TEXT private string m_Text; /// <summary> /// Gets the full method signature, file and line number. /// </summary> public string Text { get { return this.m_Text; } } #endregion TEXT #region TO STRING /// <summary> /// Gets the full method signature, file and line number. /// </summary> public override string ToString() { return this.Text; } #endregion TO STRING #region TYPE #region FULL private string m_TypeNameFull; /// <summary> /// Gets the full name of the type that contains this method, /// including the namespace. /// </summary> public string TypeNameFull { get { return this.m_TypeNameFull; } } #endregion FULL #region NORMAL private string m_TypeName; /// <summary> /// Gets the name of the type that contains this method, /// not including the namespace. /// </summary> public string TypeName { get { return this.m_TypeName; } } #endregion NORMAL #region TYPE private Type m_Type; /// <summary> /// Gets the type that contains this method. /// </summary> public Type Type { get { return this.m_Type; } } #endregion TYPE #endregion TYPE } }
To obtain information about the caller, create a CallingMethod object and access its properties as needed. For example, here is a simple console program that obtains and displays information about the Main method:
using System; namespace DevTopics { class Program { static void Main( string[] args ) { CallingMethod method = new CallingMethod(); ShowCallingMethod( method ); } static private void ShowCallingMethod( CallingMethod method ) { Console.WriteLine( "nFileName={0}", method.FileName ); Console.WriteLine( "FilePath={0}", method.FilePath ); Console.WriteLine( "LineNumber={0}", method.LineNumber ); Console.WriteLine( "MethodName={0}", method.MethodName ); Console.WriteLine( "MethodNameFull={0}", method.MethodNameFull ); Console.WriteLine( "MethodSignature={0}", method.MethodSignature ); Console.WriteLine( "MethodSignatureFull={0}", method.MethodSignatureFull ); Console.WriteLine( "Namespace={0}", method.Namespace ); Console.WriteLine( "ReturnName={0}", method.ReturnName ); Console.WriteLine( "Text={0}", method.Text ); Console.WriteLine( "TypeName={0}", method.TypeName ); Console.WriteLine( "TypeNameFull={0}", method.TypeNameFull ); Console.ReadLine(); } } }
The console output would be:
FileName=Program.cs
FilePath=D:DataVisual Studio 2005CallingMethodProgram.cs
LineNumber=9
MethodName=Main
MethodNameFull=DevTopics.Program.Main
MethodSignature=Main(System.String[])
MethodSignatureFull=Void DevTopics.Program.Main(System.String[])
Namespace=DevTopics
ReturnName=Void
Text=Void DevTopics.Program.Main(System.String[]) [File=Program.cs, Line=9]
TypeName=Program
TypeNameFull=DevTopics.Program
Using CallingMethod in Another Method
The overloaded CallingMethod constructor enables you to use the CallingMethod object inside another utility method. By providing the Type to ignore, the utility method returns information about the caller of the utility method, not the utility method itself. For example, consider this utility method that simply returns the calling method. The Utils type is passed to the CallingMethod constructor:
using System; namespace DevTopics { class Utils { /// <summary> /// Example using CallingMethod within another method. /// This still returns information about the caller /// by excluding calls from this Utils class. /// </summary> static public CallingMethod GetCallingMethod() { return new CallingMethod( typeof( Utils ) ); } } }
Then the Main() method would be rewritten as follows to use the utility method:
static void Main( string[] args ) { CallingMethod method = Utils.GetCallingMethod(); ShowCallingMethod( method ); }
As expected, the new output is identical to the output listed above.
Just need a few more regions and I think we’re there.
Hi Timm,
first thanks for providing your solution. But…
This solution provides a considerable performance hit. I picked up the code from https://stackoverflow.com/questions/4885582/methodbase-getcurrentmethod-performance and used the two methods from there (passing a delegate of the calling function and MethodBase.GetCurrentMethod() ). I also implemented a simple StackFrame-based method (new StackFrame( 1, true ).GetMethod().Name) and your method to do some runtime analysis.
Here is the result:
MethodBase Time: 12,3619ms
Delegate Time: 6,5693ms
Stack Time: 346,3704ms
CallingMethod Time: 641,3508ms
Beside the performance issue, I agree with Phil that there are seem to be far too many #regions in your code. I once read the article https://programmers.stackexchange.com/questions/118818/why-are-people-so-strongly-opposed-to-region-tags-in-methods and decided to keep my fingers off this feature.
Regards
Jörg
[…] A fantastic class is here: https://www.csharp411.com/c-get-calling-method/ […]
[…] A fantastic class is here: https://www.csharp411.com/c-get-calling-method/ […]
[…] A fantastic class is here: https://www.csharp411.com/c-get-calling-method/ […]
This code will not work in the calling method gets inlined by the JIT compiler.