在我們的編程生涯中我們要使用很多很多類庫,這些類庫有的是我們自己開 發的,我們有她的代碼,有的是第三方發布的,我們不僅沒有他們的代碼,連看 的機會都沒有。
作為.Net程序員,我們每天都要和BCL(Base Class Linbrary)打交道。無疑 ,BCL做為一個年輕的框架類庫,她是成功的,但是還有一些時候我們還是得寫 一些”Helper”方法來擴展類庫,由於我們不能修改類庫的源代碼, 我們只有寫一個個的靜態類。雖然在使用上也算方便,但作為追求完美的程序員 來說總有些不雅。現在我就碰到這樣的事情,前兩天奉命寫一個從XML文件加載 Chart圖的設置的方法,從XML加載數據綁定到對象上,這肯定是反射的用武之地 了。我經常需要寫一些根據對象屬性名字來判斷這個對象是否有這個屬性或者根 據屬性名獲取該屬性的值。還是按照平常一樣,我很快寫了一個PropertyHelper ,裡面有兩個靜態方法:HASProperty,GetValueByName。
PropertyHelper.HasProperty(point, "X"),如此的調用也還過 得去,不過在C# 3.0微軟為我們提供了擴展方法。現在我們可以直接這樣調用了 point.HASProperty(“X”);看看我是如何實現這個擴展方法的?
public static class PropertyExtension
{
public static object GetValueByName(this object self, string propertyName)
{
if (self == null)
{
return self ;
}
Type t = self.GetType();
PropertyInfo p = t.GetProperty(propertyName);
return p.GetValue(self, null);
}
}
我給object類型添加了一個擴展方法,在.Net裡所有的類都繼承自object, 那所有的類都默認的擁有這個方法了,真方便,呵呵。
注意到和普通的靜態方法有何差別?在這個方法的第一個參數前面多了一個 this關鍵字。
擴展方法:
1 方法所在的類必須是靜態的
2 方法也必須是靜態的
3 方法的第一個參數必須是你要擴展的那個類型,比如你要給int擴展一個方 法,那麼第一個參數就必須是int。
4 在第一個參數前面還需要有一個this關鍵字。
按照上面的步驟寫你就得到了一個“擴展方法”,你可以像調用 這個類的原生方法那樣去調用它:
string str = "abc";
object len = str.GetValueByName("Length");
好像string類型現在有了GetValueByName這個方法一樣,但實際上string並 沒有這樣一個方法。那這又是為什麼呢?是我們可愛的編譯器在其中做了手腳。 為了避開編譯器的干擾,我們來直接欣賞MSIL代碼:
L_0008: ldstr "Length"
L_000d: call object TestLambda.PropertyExtension::GetValueByName(object, string)
從MSIL中我們可以看出,這段代碼編譯後和調用靜態方法沒有任何的差別(從 call指令來看,這是在調用一個靜態方法)。
從這裡可以知道擴展方法即可以使用實例調用的方式也可以直接使用靜態類 調用的方式:
str.GetValueByName("Length");
PropertyExtension.GetValueByName(str,"Length");
下面將對擴展方法做一些細節的介紹:
Visual Studio 2008對擴展方法有智能感知的支持,如下圖:
在方法的圖標上有一個與其他的都不相同,他的突變下面還帶有一個藍色的 向下的箭頭,這就表明這個方法是一個擴展方法。
下面是對編寫擴展方法要注意的幾個原則(當然,仁者見仁、智者見智,這也 是一家之言):
擴展方法有就近原則,也就是如果在你的程序裡有兩個一模一樣的擴展方法 ,一個和你的使用類是處於同一命名空間裡,另外一個處於別的命名空間裡,這 個時候會優先使用同一命名空間裡的擴展方法,也就是說“血緣關系 ”越近,越被青睐。
很多人看到擴展方法也許眼裡冒出金光,以後在設計的時候不管三七二十一 ,反正可以擴展。還有一些人會對類任意擴展,將以前一些作為 ”Helper”的方法統統使用擴展方法代替,注意的是擴展方法有 “污染性”,所以我覺得在擴展的時候還是想想,是不是值得這樣擴 展。
在擴展的時候也不要對比較高層的類進行擴展,像我上面對object的擴展我 覺得就是不可取的,object是所有類的基類,一經擴展,所有的類都被“ 污染”了。