[Visual Studio][C#].NET 4.5 New Feature - Caller Information


Caller Information是.NET 4.5的新功能,它能在編譯時為我們提供些額外的資訊給副程式,像是被哪個方法叫用、叫用的方法所在的檔案位置、以及程式碼行數,我們可以用這些額外的資訊提供Log更為詳細的資訊,再也不需要用StackTrace來提供這些資訊了,不僅簡單,在效能上也會因此提升,此外也可以避免實作INotifyPropertyChanged時用字串處理把程式寫的太死,造成後續重構時重新命名有所遺漏,導致整個程式運作不如預期。



Caller Information在使用上很簡單,我們只要為方法中加入對應的選擇性參數,並在選擇性參數前加入對應的Attribute就可以了,像是CallerMemberNameAttribute可以提供呼叫的方法,CallerFileNameAttribute可以提供呼叫的方法所在的檔案位置,CallerLineNumberAttribute則是提供呼叫的方法所在的行數。我們可以看下下面的簡易範例:



private void TraceMessage(string message,
[CallerMemberName] string memberName = “”,
[CallerFilePath] string sourceFilePath = “”,
[CallerLineNumber] int sourceLineNumber = 0)
{
Console.WriteLine(“message: “ + message);
Console.WriteLine(“member name: “ + memberName);
Console.WriteLine(“source file path: “ + sourceFilePath);
Console.WriteLine(“source line number: “ + sourceLineNumber);
}



使用上我們可以撰寫像下面這樣的程式呼叫上面的提到的方法,這邊只填入必要參數:



public CallerTestClass()
{
TraceMessage(“Constructor”);
}



運行起來會像下面這樣子,可以看到呼叫的方法名稱、檔案位置、行數都正確的顯示了。


image



查看一下反組譯的結果,我們可以看到在編譯完的IL碼中就已經帶好了這些資訊,代表這是編譯器幫我們在編譯階段做掉的事,而不像StackTrace是在執行階段去做,效能上會有所差異。


image



讓我們回到本來的範例,這邊很好玩的是,Caller Information在使用時,IntelliSense看起來就跟一般有選擇性參數的方法一樣,我們從IntelliSense看不出來是有用到Caller Information,所以這邊在參數的命名上要讓使用者明確的知道這件事。


image



另外既然是用選擇性參數,那麼有人會說若是自己將參數帶入會怎麼樣?


image



這樣做的話它會失去Caller Information的效果,跑出來的會變成都是自己帶入的值。



image



所以使用上這邊也要特別注意,看到有使用到Caller Information的地方就要避開帶入不該帶入的參數,這邊看起來微軟並沒有在這塊做些警示,自己在做類別時最好把這邊封裝的好一點、裡面一點。



另外要注意的是,在取得呼叫的方法名稱那邊,呼叫的方法名稱會依使用的地方不同而有所差異。在方法、屬性、事件中我們運行得到的會是方法、屬性、事件的名稱,在建構子取得的是”.ctor”、在靜態建構子取得的是”.cctor”、解構子是”Finalize”、運算子又更為特殊了,這邊可參考MSDN上的說明:


image



也可以回到上面反組譯的那張圖,基本上它取得的名稱大概跟反組譯出來看到的成員是一樣的。



最後這邊附上比較完整的測試範例,這個範例也可以帶出CallerMemberNameAttribute在不同地方叫用所得到的結果,有興趣可以稍微留意一下:



using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var obj = new CallerTestClass();
}
}

class CallerTestClass
{
    event EventHandler Event;

    private Boolean _temp = TraceMessage("Init temp var");
    private int _property;

    public int Property
    {
        get
        {
            TraceMessage("getProperty");
            return _property;
        }
        set
        {
            TraceMessage("setProperty");
            _property = value;
        }
    }

    static CallerTestClass()
    {
        TraceMessage("Static Constructor");
    }

    public CallerTestClass()
    {
        TraceMessage("Constructor");
        Property = 0;
        Method();

        Event += CallerTestClass_Event;
        OnEvent(EventArgs.Empty);

        var temp = !this;
    }

    void CallerTestClass_Event(object sender, EventArgs e)
    {
        TraceMessage("CallerTestClass_Event");
    }

    ~CallerTestClass()
    {
        TraceMessage("DeConstructor");
    }

    protected void OnEvent(EventArgs e)
    {
        if (Event == null)
            return;

        TraceMessage("OnEvent");
        Event(this, e);
    }

    public void Method()
    {
        TraceMessage("Method");
    }

    private static bool TraceMessage(string message,
        [CallerMemberName] string memberName = "",
        [CallerFilePath] string sourceFilePath = "",
        [CallerLineNumber] int sourceLineNumber = 0)
    {
        Console.WriteLine("message: " + message);
        Console.WriteLine("member name: " + memberName);
        Console.WriteLine("source file path: " + sourceFilePath);
        Console.WriteLine("source line number: " + sourceLineNumber);
        Console.WriteLine();
        return true;
    }

    public static CallerTestClass operator !(CallerTestClass obj)
    {
        TraceMessage("Operator !");
        return null;
    }
}

}



運行的結果如下:


image


image



Link



  • Caller Information (C# and Visual Basic)