看到这篇文章:http://tech.pro/tutorial/1155/obtaining-method-caller-information-in-c 感觉挺有用处,就节选重点翻译下。
在日志组件中,我们可能需要记录方法调用信息。.NET 4.5/Visual Studio 2012提供了很方便地支持这一功能。
要记录的方法调用信息包括:
- 方法成员名称
- 源文件路径
- 行号
为了获取这些信息,我们只需要使用System.Runtime.CompilerServices命名空间下的CallerMemberName、CallderFilePath和CallderLineNumber注解,如下:
public static void Log(string msg, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) { string msgToLog = string.Format("{0} ({1} line {2}): {3}", memberName, filePath, lineNumber, msg); Trace.WriteLine(msgToLog); }
然后我们在业务逻辑代码中这样调用:
class BusinessLogic { public void PerformLogic() { // do logic Logger.Log("Finished performing logic."); } }
运行之后我们可以看到下面输出:
PerformLogic (c:\Project\Program.cs line 26): Finished performing logic.
代码很简单,不多解释了。
工作原理
上面代码背后的工作原理是什么呢?概括成一句话:编译器帮了我们大忙!
当编译器遇到某个方法的参数带有上面的注解时,参数的值被忽略,它将自动为其提供值。比如下面是上面的PerformLogic方法的反编译结果(IL代码):
.method public hidebysig instance void PerformLogic() cil managed { .maxstack 8 IL_0000: ldstr "Finished performing logic." IL_0005: ldstr "PerformLogic" IL_000a: ldstr "c:\\Project\\Program.cs" IL_000f: ldc.i4.s 26 IL_0011: call void ConsoleApplication1.Logger::Log(string, string, string, int32) IL_0016: ret } // end of method BusinessLogic::PerformLogic
我们可以看出方法名、源文件路径和行号都被标识为常量(在编译期间完成)。
我们再来看一个例子:当属性值发生改变时通知改变。
先定义一个事件处理接口:
public interface INotifyPropertyChanged { event PropertyChangedEventHandler PropertyChanged; }
接口定义了一个事件,当属性值发生改变时会触发这个事件。然后我们让要监听属性值变化的类实现这个接口:
class Person : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private string firstName; private string lastName; public string FirstName { get { return this.firstName; } set { this.firstName = value; NotifyPropertyChanged("FirstName"); } } public string LastName { get { return this.lastName; } set { this.lastName = value; NotifyPropertyChanged("LastName"); } } private void NotifyPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
每当调用Person对象的FirstName或者LastName属性的setter方法时就会调用私有方法NotifyPropertyChanged方法,然后这个方法内部再触发属性改变事件。
但是这里propertyName并不是必须的,如果我们使用[CallerMemberName]的话,如下:
public string LastName { get { return this.lastName; } set { this.lastName = value; NotifyPropertyChanged(); } } private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } }
现在由编译器编译时自动帮我们设定propertyName的值。
注意上面讲的特性必须在.NET 4.5/Visual Studio 2012及以上版本才支持,在此之前可以用下面的替代方案(但不推荐这种方案):
public static void Log(string msg) { StackFrame stackFrame = new StackFrame(1); string methodName = stackFrame.GetMethod().Name; string fileName = stackFrame.GetFileName(); int lineNumber = stackFrame.GetFileLineNumber(); string msgToLog = string.Format("{0} ({1} line {2}): {3}", methodName, fileName, lineNumber, msg); System.Diagnostics.Trace.WriteLine(msgToLog); }