程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#基礎知識 >> C#的擴展方法、以及探討擴展方法的本質、注意事項

C#的擴展方法、以及探討擴展方法的本質、注意事項

編輯:C#基礎知識

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版)

由於本人才學識淺,描述難免纰漏,如有錯誤,歡迎指出。麼麼!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved