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

  • 6194
  • 0
  • C#
  • 2013-09-13

[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