[.NET]快快樂樂學LINQ系列前哨戰-擴充方法(Extension Method)
在LINQ裡面,有一個很重要的基本feature,就是擴充方法(Exension Method),透過簡單的設計方式,簡單的引用命名空間,就可以在不改變現有系統結構下,使用新的方法。這邊就來對擴充方法作個簡單的說明。
擴充方法的簡介
首先,請參考MSDN的說明:擴充方法 (C# 程式設計手冊)
擴充方法讓您能將方法「加入」至現有型別,而不需要建立新的衍生型別 (Derived Type)、重新編譯,或是修改原始型別。 擴充方法是一種特殊的靜態方法,但是需將它們當成擴充型別上的執行個體方法 (Instance Method) 來呼叫
簡單的說,『可以新增方法到現有型別上,而不用修改原本型別或重新編譯,就可以使用新的方法』。
如何設計擴充方法
如MSDN上所描述:
擴充方法是定義為靜態方法,但使用執行個體方法語法進行呼叫。 擴充方法的第一個參數指定方法進行作業的型別,而這個參數的前面需加上 this 修飾詞 (Modifier)。 使用 using 指示詞,將命名空間 (Namespace) 明確匯入至原始程式碼時,擴充方法才會進入範圍中。
幾個關鍵字highlight一下:
- 靜態方法
- 使用執行個體方法語法呼叫
- 第一個參數代表進行作業的型別
-
第一個參數前面加上
this
修飾詞 - 使用using引用命名空間,才可以使用擴充方法
我們舉個例子來說明這些highlight指的是什麼。
namespace ExtensionSample.MyExtension
{
public static class JoeyExtension
{
public static int maskLength = 3;
public static string maskSignal = "x";
public static string MaskContent(this string original)
{
var result = string.Empty;
if (original.Length > maskLength)
{
var plainText = original.Substring(0, maskLength);
var maskCount = original.Length - maskLength;
var encodingText = string.Join(string.Empty, Enumerable.Repeat<string>(maskSignal, maskCount).ToArray<string>());
result = plainText + encodingText;
}
else
{
result = original;
}
return result;
}
}
}
宣告擴充方法的方式,以及其代表的意義:
使用上既可以用靜態方法的方式來呼叫,也可以使用擴充方法的方式來呼叫。擴充方法的方式,則是針對extend的型別的執行個體,就可以呼叫設計的擴充方法。 而以使用擴充方法來說,根本就不care這個靜態類別。因為使用上會直接引用擴充方法的namespace,接著直接在其extend的型別上,直接呼叫擴充方法。請見下面的例子:
static void Main(string[] args)
{
var target = "joey123456789";
var result = target.MaskContent();
Console.WriteLine("呼叫擴充方法結果:{0}", result);
var resultByStatic = MyExtension.JoeyExtension.MaskContent(target);
Console.WriteLine("呼叫靜態方法結果:{0}", resultByStatic);
}
可以看到兩個執行的結果完全相同。
因為擴充方法屬於該靜態類別,所以實際使用上,是可以存取靜態成員的。也就是呼叫端可以更改靜態成員,來改變擴充方法的內容。(但實務上,不建議這麼做,除非有其必要性,否則讓擴充方法有side effect)
static void Main(string[] args)
{
var target = "joey123456789";
var result = target.MaskContent();
Console.WriteLine("呼叫擴充方法結果:{0}", result);
JoeyExtension.maskLength = 5;
var result2 = target.MaskContent();
Console.WriteLine("修改靜態變數『遮罩長度』為5後,呼叫擴充方法結果:{0}", result2);
JoeyExtension.maskSignal = "o";
var result3 = target.MaskContent();
Console.WriteLine("修改靜態變數『遮罩符號』為o後,呼叫擴充方法結果:{0}", result3);
}
補充說明一下,這邊的例子剛好是針對string型別,且擴充方法回傳的型別也是string,這並不代表這兩者型別必須相同。例如可以針對DateTime型別設計擴充方法,以產生不同的string結果(回傳特定格式的年月,回傳距離現在多久時間等等…)。
結論
Q1: 什麼時候使用擴充方法?
擴充方法最大的用處在於,不必修改原本既有存在的型別內容(包含interface),就可以將方法內容直接附加到該型別上。
Q2: 哪裡可以使用擴充方法?
只要有using擴充方法所在的namespace,即可使用該擴充方法。
Q3: 擴充方法的目的?
針對型別來設計方法,將方法內容封裝起來,讓使用上、維護上都更加直覺與彈性。且不會破壞原本型別內的封裝性,因為擴充方法無法修改型別內部資訊。
Q4: 擴充方法的注意事項。
擴充方法如果濫用,可能反而會造成維護上的困擾。因為呼叫某一個class的方法,但其方法內容卻不一定定義在其class內,而可能存在於外部靜態類別中。建議擴充方法的類別、命名空間以及存放的目錄結構,應加以定義和規範,避免團隊協同合作時,擴充方法類別散落一地。而針對的型別如果太底層(例如object),或是每次用到這個型別都不一定適用該擴充方法,則可能造成設計上的雜訊。
最後,擴充方法針對的是型別,而型別就與物件導向的繼承有關。這邊的繼承指的是什麼?舉例來說,假設我們的擴充方法所extend的型別為object,而所有物件都是繼承自Object,那就代表,所有物件都可以使用這一個擴充方法。
在LINQ中,LINQ to Objects的方法內容,大部分都寫在System.Linq裡的Enumerable
這個static class中,而裡面絕大部分,都是針對IEnumerable<T>
這個泛型介面的擴充方法。也就是,只要有引用System.Linq這個命名空間,且繼承或實作了IEnumerable<T>這個介面,就可以使用這些擴充方法。例如List<T>
, IList<T>
, Dictionary<TKey,TValue>
等等常見的泛型資料集合結構,都有實作IEnumerable<T>
。
而有一些非泛型的資料集合結構,則通常都有實作IEnumerable這個介面(也就是可以使用foreach的資料結構),在Enumerable的靜態方法中,有針對IEnumerable介面撰寫的擴充方法:Cast<TResult>
,以及OfType<TResult>
等,透過這些方法,就可以取得IEnumerable<T>
的結構,接著就可以使用LINQ的方法。例如Array, NameValueCollection,甚至針對頁面的Controls,就都可以透過類似這樣的方法來享受LINQ的好處。
LINQ的設計上,真的將擴充方法發揮到了極致,透過擴充方法的機制與設計,.net framework不需要去修改之前這些資料集合類別,開發人員也不需要去記憶或使用巢狀的靜態方法,就可以完成LINQ的功能。
Sample code: ExtensionSample.zip
blog 與課程更新內容,請前往新站位置:http://tdd.best/