看到这篇文章: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);
}
posted on 2013-04-18 18:12  feichexia  阅读(2233)  评论(1编辑  收藏  举报