擴充套件方法 - 概述

擴充套件方法在 C#3.0 中引入。擴充套件方法擴充套件並向現有型別新增行為,而不建立新的派生型別,重新編譯或以其他方式修改原始型別。*當你無法修改要增強的型別的源時,它們尤其有用。*可以為系統型別,由第三方定義的型別以及你自己定義的型別建立擴充套件方法。可以呼叫擴充套件方法,就好像它是原始型別的成員方法一樣。這允許方法鏈用於實現 Fluent 介面

通過向靜態類新增靜態方法來建立擴充套件方法,該靜態類與要擴充套件的原始型別不同。持有擴充套件方法的靜態類通常是為了儲存擴充套件方法而建立的。

擴充套件方法採用特殊的第一個引數來指定要擴充套件的原始型別。第一個引數用關鍵字 this(它構成了在 C#中的 this 的特殊和獨特用途 - 應理解為與使用 this 不同,後者允許引用當前物件例項的成員)。

在以下示例中,要擴充套件的原始型別是類 stringString 已通過 Shorten() 方法進行了擴充套件,該方法提供了縮短的附加功能。已建立靜態類 StringExtensions 以儲存擴充套件方法。擴充套件方法 Shorten() 通過特別標記的第一個引數顯示它是 string 的擴充套件。為了表明 Shorten() 方法擴充套件 string,第一個引數用 this 標記。因此,第一個引數的完整簽名是 this string text,其中 string 是擴充套件的原始型別,text 是所選擇的引數名稱。

static class StringExtensions
{
    public static string Shorten(this string text, int length) 
    {
        return text.Substring(0, length);
    }
}

class Program
{
    static void Main()
    {
        // This calls method String.ToUpper()
        var myString = "Hello World!".ToUpper();

        // This calls the extension method StringExtensions.Shorten()
        var newString = myString.Shorten(5); 

        // It is worth noting that the above call is purely syntactic sugar
        // and the assignment below is functionally equivalent
        var newString2 = StringExtensions.Shorten(myString, 5);
    }
}

.NET 小提琴現場演示

作為擴充套件方法第一個引數傳遞的物件 (伴隨著 this 關鍵字)是呼叫擴充套件方法的例項。

例如,執行此程式碼時:

"some string".Shorten(5);

引數的值如下:

text: "some string"
length: 5

請注意,擴充套件方法僅在與其定義位於同一名稱空間中時才可用,如果使用擴充套件方法由程式碼顯式匯入名稱空間,或者擴充套件類是無名稱空間。 .NET 框架指南建議將擴充套件類放在它們自己的名稱空間中。但是,這可能會導致發現問題。

這導致擴充套件方法和正在使用的庫之間沒有衝突,除非明確地引入可能衝突的名稱空間。例如 LINQ Extensions

using System.Linq; // Allows use of extension methods from the System.Linq namespace

class Program
{
    static void Main()
    {
        var ints = new int[] {1, 2, 3, 4};

        // Call Where() extension method from the System.Linq namespace
        var even = ints.Where(x => x % 2 == 0); 
    }
}

.NET 小提琴現場演示

從 C#6.0 開始,也可以將 using static 指令放到包含擴充套件方法的中。例如,using static System.Linq.Enumerable;。這使得來自該特定類的擴充套件方法可用,而無需將來自同一名稱空間的其他型別引入範圍。

當具有相同簽名的類方法可用時,編譯器將其優先於擴充套件方法呼叫。例如:

class Test
{
   public void Hello()
   {
       Console.WriteLine("From Test");
   }
}

static class TestExtensions
{
    public static void Hello(this Test test)
    {
        Console.WriteLine("From extension method");
    }
}

class Program
{
    static void Main()
    {
        Test t = new Test();
        t.Hello(); // Prints "From Test"
    }
}

.NET Fiddle 上的現場演示

請注意,如果有兩個具有相同簽名的擴充套件函式,並且其中一個在同一名稱空間中,那麼將優先考慮該名稱。另一方面,如果 using 訪問了它們,那麼編譯時錯誤將隨之發生:

以下方法或屬性之間的呼叫不明確

請注意,通過 originalTypeInstance.ExtensionMethod() 呼叫擴充套件方法的語法方便是一個可選的便利。該方法也可以以傳統方式呼叫,以便將特殊的第一引數用作該方法的引數。

即,以下兩項工作:

//Calling as though method belongs to string--it seamlessly extends string
String s = "Hello World";
s.Shorten(5);  

//Calling as a traditional static method with two parameters
StringExtensions.Shorten(s, 5);