在動態編程時,我們常常需要運行時確定調用對象的哪個屬性或哪個方法。這個任務通常可以用反射來解決。但眾所周知,反射的效能要比靜態指定的方式低很多,因為反射要通過運行時複雜的機制完成。能否獲得效能和靈活性兼備的動態調用?我在開發VBF的最新功能時反覆考慮了這個問題。我們通常動態調用一個對象的屬性是採用這樣的手法,假設對象a有一個屬性叫做MyProp:
Type t = a.GetType();
PropertyInfo pi = t.GetProperty("MyProp");
string value = (string)pi.GetValue(a, null);
注意到什麼問題了嗎?我們知道這個屬性的類型是string,也知道它沒有參數。當然也有不知道即將調用的屬性類型及參數的時候,但這個場合我們知道,卻沒有利用,還是當成什麼資訊都不知道一樣使用純動態手法擷取。這樣我們就錯失了能利用強型別特性加速這一過程的良機。同樣還有方法調用,我們有時候只是方法或屬性的名字在編譯時間不知道(比如需要使用者指定),但方法或屬性的類型及簽名我們是知道的,這種情況下就可以用泛型和委託技術高效能地調用。
泛型技術為處理類型提供了方便,除此之外,.NET的委託還具有一些額外的良好特性。委託可以擔當類似介面的任務,但與介面最大的不同就在於,方法無須聲明自己滿足某個委託,而只要簽名符合,即可賦給委託變數。這樣我們就可以利用一組事先聲明的委託,處理千變萬化類型的屬性與方法。
C#不允許屬性帶有參數,除非是索引器。VB允許屬性帶有參數但很少有人真的大量使用。於是在真實世界中屬性的getter和setter的形式就被限定了,絕大部分屬性的getter和setter可以用以下兩個委託表示:
public delegate void PropertySetter<T>(T value);
public delegate T PropertyGetter<T>();
有了這兩個委託,我們就可以對已知類型但名字需要動態化的屬性進行高速的強型別動態訪問了。方法是使用反射擷取屬性Gettet或Setter的MethodInfo,再使用MethodInfo建立委託:
Type t = a.GetType();
PropertyInfo pi = t.GetProperty("MyProp");
MethodInfo getter = pi.GetGetMethod();
PropertyGetter<string> strPropGetter =
(PropertyGetter<string>)Delegate.CreateDelegate(
typeof(PropertyGetter<string>), a, getter);
string value = strPropGetter();
注意,這個方法在調用前進行了更多反射操作,因此,如果你只想一兩次地擷取屬性的值,這種方法還不如直接用放射來的快。但是,當你需要對同一屬性進行成千上萬次訪問時,絕對值得多寫這點代碼,在string類型的簡單屬性上,速度可比直接反射擷取最多快達1000倍,這是我實測的結果。
接下來我們討論有index的屬性和方法的調用。C#盡允許在索引器的文法上使用屬性參數,而在VB看來,索引器不過是類所有帶參數的屬性中比較特殊的一個,他得到了在對象上使用數組文法訪問的特權。不管怎麼說,無論是索引器還是普通帶參數的屬性,他們的getter和setter過程都不像典型屬性那樣簡單。同樣還有對方法的調用,方法的簽名千變萬化,似乎我們很難用預先定義的委託統一進行調用。事實的確如此,不過與針對每一種屬性訪問器或方法的簽名定義一種委託的做法相比,泛型還是給出了一種稍微舒服一點的做法:
public delegate R Func<R>();
public delegate R Func<T0, R>(T0 a0);
public delegate R Func<T0, T1, R>(T0 a0, T1 a1);
public delegate R Func<T0, T1, T2, R>(T0 a0, T1 a1, T2 a2);
這樣一組泛型委派,可以涵蓋參數數目從0-3,有傳回值並且沒有參數是out或ref的所有方法簽名。你還可以定義一組用於無傳回值的。有了這樣一組泛型委派,就可以在想要某種函數的簽名時直接建立出來,而無須聲明新的類型。再結合剛才的手法,就可以用統一的手法實現大部分帶有參數的屬性或方法的動態調用——同時獲得動態名稱和強型別效能的雙重好處。
也許你早已經利用了類似的手法,並用於除了動態調用屬性或方法以外的其他任務。我只是在開發VBF時想到了他們,希望能對部分需要的人有所協助。