C#掃盲之:帶你掌握C#的擴展方法、以及探討擴展方法的本質、注意事項

一、爲何須要擴展方法


 

.NET3.5給咱們提供了擴展方法的概念,它的功能是在不修改要添加類型的原有結構時,容許你爲類或結構添加新方法。程序員

思考:那麼究竟爲何須要擴展方法呢,爲何不直接修改原有類型呢?數組

首先,假設咱們的項目中有一個類,後來過了一段時間,咱們明確的知道須要爲該類添加一個新功能,考慮這個需求有兩個解決辦法:ide

(1)直接修改當前類的定義函數

  這樣作的缺點是,破壞向後的兼容性,可能之前使用的舊代碼沒法經過編譯。好比說舊代碼使用了一個Methed(int,int)的方法,可是爲了知足新功能咱們如今修改爲了Methed(int,int,int),多增長了一個參數,這樣原有的舊代碼就沒法經過編譯。ui

(2)以當前類爲基類進行派生,在子類中進行實現this

  這樣作也有缺點,那就是假如功能須要修改時,咱們須要維護兩個地方,一個是父類,一個是子類,增長了代碼維護工做量spa

這時,新的特性擴展方法解決了以上兩個問題,而且還解決了當有些類的實現是第三方的,咱們沒法修改源代碼狀況下,以及某些類是不可繼承的,沒法派生的,這兩種狀況下任然可使用擴展方法來添加新功能。使用擴展方法,能夠在不建立子類和直接修改類型的狀況下修改類型。code

 

二、擴展方法怎麼用


 

2.1規則

定義擴展方法必須遵照兩個Static和一個this對象

1.必須把擴展方法定義在靜態類中,每一個擴展方法也必須聲明爲靜態的
2.全部擴展方法必需要使用this關鍵字對第一個參數進行修飾

 

 

擴展方法的實現以下圖所示,咱們要給StringBuilder系統類型擴展一個功能用於提取字符串對象中的某個字符的索引blog

 

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關鍵字修飾,其餘的參數視爲方法的普通參數。

 

三、擴展方法的定義位置


定義擴展方法,也就是說定義擴展方法靜態類時,咱們必須爲其指定命名空間,若是該命名空間與要使用擴展方法的命名空間不一樣,則須要導入命名空間(使用using關鍵字)。建議擴展方法定義成將要擴展類型的相同的命名空間,好比咱們上面例子中的系統類型StringBulder的命名空間爲System.Text,咱們徹底能夠添加一個新類,而後定義命名空間爲System.Text,也就是說最好是全局的、最外層的命名空間,這樣作的好處咱們將在下面闡述。

 

四、擴展方法的本質


 

擴展方法的實質實際上是由編譯器來採用鏡像原理來實現的,並無改變原有類型或者是附加什麼額外的東西,查看下圖譯生成後的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版)
因爲本人才學識淺,描述不免紕漏,若有錯誤,歡迎指出。麼麼!
相關文章
相關標籤/搜索