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

来源:http://www.cnblogs.com/Matcha/archive/2016/03/31/5312288.html
-Advertisement-
Play Games

1、為什麼需要擴展方法 .NET3.5給我們提供了擴展方法的概念,它的功能是在不修改要添加類型的原有結構時,允許你為類或結構添加新方法。 思考:那麼究竟為什麼需要擴展方法呢,為什麼不直接修改原有類型呢? 首先,假設我們的項目中有一個類,後來過了一段時間,我們明確的知道需要為該類添加一個新功能,考慮這 ...


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

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近一直在調試視頻 採集卡,和PC端連接的是USB介面,使用的是cypress的CY7C68013A-56PVXC,現列舉一下對於CY7C68013無法識別的可能存在原因。在設備管理器裡面顯示的VID/PID為全0。 上圖是CY7C68013A-56PVXC和EEPROM的最小系統。 若是按照上圖接 ...
  • 雖然ubuntu的中國伺服器的速度已經非常不錯,但是難免,會有網路不暢的情形,所以修改軟體源地址是一個基礎的知識點。 修改ubuntu的軟體源的方式有多種,一直是通過ubuntu軟體中心提供的UI,還有一種就是通過編輯/etc/apt/sources.list配置文件。 UI操作 通過軟體中心配置軟... ...
  • SCCM 部署操作系統 ,提示許可權問題,報錯:0xc00000098 這個問題通過查看日誌,論壇搜索,博客等。下麵是最終的解決方法: 1.檢查所有組件是否已安裝。(IIS組件等)。(分發站點證書是否正常) 2.在分發點將PEX鉤去掉。 3.卸載WDS重啟SCCM伺服器。 4.卸載管理組件。 5.卸載 ...
  • 自從甲骨文收購 MySQL 後,由於甲骨文對 MySQL 的開發和維護更多傾向於閉門的立場,很多 MySQL 的開發者和用戶放棄了它。在社區驅動下,促使更多人移到 MySQL 的另一個叫 MariaDB 的分支,在原有 MySQL 開發人員的帶領下,MariaDB 的開發遵循開源的理念,並確保它的二 ...
  • 之前的折騰過Windows下ssh的自動登錄,比如這篇Windows下使用Xshell建立反向隧道,但是這個不能無交互的情況下自動連接(比如在連接新主機時),也就很難在服務中使用。解決方法還是得使用命令行版本。 OpenSSH其實是有Windows版本的,並且目前還在維護:https://githu ...
  • 我們知道VMware的網路適配器類型有多種,例如E1000、VMXNET、VMXNET 2 (Enhanced)、VMXNET3等,就性能而言,一般VMXNET3要優於E1000,下麵介紹如果將Linux虛擬機的網路適配器類型從E1000改為VMXNET3。本文測試環境如下 操作系統 :Oracle... ...
  • 顯示層面頭文件 定義結構體,為顯示統一標準 int (*DeviceInit)(void); 顯示類驅動初始化 int (*ShowPixel)(int iPenX, int iPenY, unsigned int dwColor); 對某一點進行瞄色 int (*CleanScreen)(unsi... ...
  • 先寫一個完善的電子書框架,完善之前寫的功能拼接到一塊,實現多種文本文件格式打開解碼顯示, 多種字體顯示,設置文字大小,將以前混亂的程式按塊封裝 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...