[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");
}
運行起來會像下面這樣子,可以看到呼叫的方法名稱、檔案位置、行數都正確的顯示了。
查看一下反組譯的結果,我們可以看到在編譯完的IL碼中就已經帶好了這些資訊,代表這是編譯器幫我們在編譯階段做掉的事,而不像StackTrace是在執行階段去做,效能上會有所差異。
讓我們回到本來的範例,這邊很好玩的是,Caller Information在使用時,IntelliSense看起來就跟一般有選擇性參數的方法一樣,我們從IntelliSense看不出來是有用到Caller Information,所以這邊在參數的命名上要讓使用者明確的知道這件事。
另外既然是用選擇性參數,那麼有人會說若是自己將參數帶入會怎麼樣?
這樣做的話它會失去Caller Information的效果,跑出來的會變成都是自己帶入的值。
所以使用上這邊也要特別注意,看到有使用到Caller Information的地方就要避開帶入不該帶入的參數,這邊看起來微軟並沒有在這塊做些警示,自己在做類別時最好把這邊封裝的好一點、裡面一點。
另外要注意的是,在取得呼叫的方法名稱那邊,呼叫的方法名稱會依使用的地方不同而有所差異。在方法、屬性、事件中我們運行得到的會是方法、屬性、事件的名稱,在建構子取得的是".ctor"、在靜態建構子取得的是".cctor"、解構子是"Finalize"、運算子又更為特殊了,這邊可參考MSDN上的說明:
也可以回到上面反組譯的那張圖,基本上它取得的名稱大概跟反組譯出來看到的成員是一樣的。
最後這邊附上比較完整的測試範例,這個範例也可以帶出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;
}
}
}
運行的結果如下: