標籤:class items 也有 item 檢查 額外 資料 his 子類
1、為什麼需要擴充方法
.NET3.5給我們提供了擴充方法的概念,它的功能是在不修改要添加類型的原有結構時,允許你為類或結構添加新方法。
思考:那麼究竟為什麼需要擴充方法呢,為什麼不直接修改原有類型呢?
首先,假設我們的項目中有一個類,後來過了一段時間,我們明確的知道需要為該類添加一個新功能,考慮這個需求有兩個解決辦法:
(1)直接修改當前類的定義
這樣做的缺點是,破壞向後的相容性,可能以前使用的舊代碼無法通過編譯。比如說舊代碼使用了一個Methed(int,int)的方法,但是為了滿足新功能我們現在修改成了Methed(int,int,int),多增加了一個參數,這樣原有的舊代碼就無法通過編譯。
(2)以當前類為基類進行派生,在子類中進行實現
這樣做也有缺點,那就是假如功能需要修改時,我們需要維護兩個地方,一個是父類,一個是子類,增加了代碼維護工作量
這時,新的特性擴充方法解決了以上兩個問題,並且還解決了當有些類的實現是第三方的,我們無法修改原始碼情況下,以及某些類是不可繼承的,無法派生的,這兩種情況下任然可以使用擴充方法來添加新功能。使用擴充方法,可以在不建立子類和直接修改類型的情況下修改類型。
2、擴充方法怎麼用 2.1規則
定義擴充方法必須遵守兩個Static和一個this:
1.必須把擴充方法定義在靜態類中,每個擴充方法也必須聲明為靜態
2.所有擴充方法必須要使用this關鍵字對第一個參數進行修飾
擴充方法的實現如所示,我們要給StringBuilder系統類別型擴充一個功能用於提取字串對象中的某個字元的索引
2.1在執行個體層次上調用擴充方法
在執行個體層次上調用擴充方法的意思就是,在被擴充項物件的執行個體上進行調用而不是使用我們定義的靜態類調用。具體怎樣調用,請看一下代碼。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ExStensionMethd{ class Program { static void Main(string[] args) { StringBuilder tmpStr = new StringBuilder("12323"); Console.WriteLine(tmpStr.StringIndef(‘2‘));//這裡我們使用的是StringBuilder的執行個體tmpStr來直接調用 Console.ReadKey(); } } /// <summary> /// 擴充方法靜態類 /// </summary> static class ExtionClass { public static int StringIndef(this StringBuilder str, char tmpChar) { int index = 0; for (; index < str.Length; index++) { if (str[index].Equals(tmpChar)) { return index; } } return -1; } }}
說明:需要特別說明的是,擴充方法可以擁有多個參數,但第一個參數的位置始終是屬於為擴充項物件的,不能改變。也就是說只有第一個參數可以並且必須用this關鍵字修飾,其他的參數視為方法的普通參數。
3、擴充方法的定義位置
定義擴充方法,也就是說定義擴充方法靜態類時,我們必須為其指定命名空間,如果該命名空間與要使用擴充方法的命名空間不同,則需要匯入命名空間(使用using關鍵字)。建議擴充方法定義成將要擴充類型的相同的命名空間,比如我們上面例子中的系統類別型StringBulder的命名空間為System.Text,我們完全可以添加一個新類,然後定義命名空間為System.Text,也就是說最好是全域的、最外層的命名空間,這樣做的好處我們將在下面闡述。
4、擴充方法的本質
擴充方法的實質其實是由編譯器來採用鏡像原理來實現的,並沒有改變原有類型或者是附加什麼額外的東西,查看譯產生後的IL代碼,可以看到我們用紅色框標記出來的地址,call int32 ExStensionMethd.ExtionClass::StringIndef(class [mscorlib]System.Text.StringBuilder,char)其實質仍然調用的原靜態類的公用靜態方法而已。
所以代碼中的tmpStr.StringIndef(‘2‘))和ExtionClass.StringIndef(tmpStr,‘2‘)是等效的,然而為什麼我們可以直接使用執行個體調用呢,這是因為編譯器默默地做了工作。
編譯器工作---------------------------------------
tmpStr.StringIndef(‘2‘)
當編譯看到以下代碼,編譯器分兩步工作:
(1) 編譯器檢查tmpStr當前類型,也就是StringBulder類以及StringBulder任何基類是否具有所匹配的名為StringIndef包含一個char參數的函數,如果找到,則產生IL代碼並Call它;
(2) 如果沒有找到匹配的方法,就繼續檢查是否有任何靜態類定義了名為StringIndef的靜態方法,並且這個方法必須第一個參數是用this關鍵字修飾,參數類型為StringBulder的。找到時產生相應的IL代碼來調用它
所以這正是我們定義擴充方法的意義,因為編譯器就是按照規則來匹配相應的方法為其產生IL代碼,然而就算我們定義了擴充方法,其他的程式員也不知道,這樣豈不是多此一舉嗎,不是這樣。作為宇宙對強大編譯器的VS,它使用了"智能感知“的功能來簡化我們對擴充方法的使用,當我們使用擴充類型執行個體時,當點號按下時,VS自動添加上擴充方法讓我們選擇,對我們程式員來說,就好像是直接使用了擴充項物件的原有方法一樣。這就是VS編譯器為我們所做的。
5.使用擴充方法擴充各種類型
擴充方法的擴充項物件可以是類,介面,委託類型等。當擴充介面時,則所有實現了此介面的類,都擁有此擴充方法。
class Program { static void Main(string[] args) { "123123123123".ShowItems();//字串 new[] { 1, 2, 3, 4, }.ShowItems();//int數組 new List<int> { 1, 2, 3, 4 }.ShowItems();//List容器 Console.ReadKey(); } } /// <summary> /// 擴充方法靜態類 /// </summary> static class ExtionClass { public static void ShowItems<T>(this IEnumerable<T> colletion) { foreach (var item in colletion) { if (item is string) Console.WriteLine(item); else Console.WriteLine(item.ToString()); } } }
6.擴充方法使用注意事項
- C#支援擴充方法,不支援擴充屬性、擴充事件、擴充操作符等;
- 擴充類必須在非泛型靜態類中定義,不能是泛型類,擴充類名可以任意定義。【為什麼不能是泛型靜態類知道嗎?如果真的不知道,說明上面的內容你沒有仔細看嘞,因為擴充方法其實再是編譯時間進行的匹配和編譯,而泛型類只有在運行時才可以進行真正確定它具體類型,所以就不能是泛型靜態類了】;
- 擴充方法的定義必須具有檔案範圍,也就是說必須在檔案中某個命名空間下直接定義,不能嵌套在另一個類中定義;
- 多個靜態類可以定義相同的擴充方法,這一點需要注意在調用時要明確調用對象。調用衝突時,不能再再使用執行個體調用。只能使用靜態類.方法的普通方式進行使用;
- 假如擴充的這個類是一個基類時,而它又有很多衍生類別,則其衍生類別也擁有這個擴充方法(正如上面我們擴充的介面一樣),這有個好處也有壞處,好處就是子類可以使用這個方法,壞處就是智能提示會有遞迴下去,有很多填充的垃圾資訊;
- 擴充方法存在版本問題。正如我們前面所說,編譯時間會首先尋找這個類是否定義了相匹配的方法,然後才回去尋找靜態類。所以假如現在我們定義了一個IndexOf的方法,而在微軟的後續版本中,官方添加了同樣名為IndexOf的方法,則我們的靜態方法就不會調用。
參考資料:精通C#(第6版)
CLR via C#(第4版)
【轉自】http://www.cnblogs.com/Matcha/p/5312288.html
【轉載】C#掃盲之:帶你掌握C#的擴充方法、以及探討擴充方法的本質、注意事項