擴充方法 (C# 程式設計手冊)Extension Methods (C# Programming Guide)

擴充方法可讓您在現有類型中「加入」方法,而不需要建立新的衍生類型、重新編譯,或是修改原始類型。Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. 擴充方法是一種特殊的靜態方法,但是會將它們當成擴充類型上的執行個體方法來呼叫。Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type. 對於以 C#、F# 和 Visual Basic 撰寫的用戶端程式碼,呼叫擴充方法或是在類型中實際定義的方法,兩者之間並沒有明顯的差別。For client code written in C#, F# and Visual Basic, there is no apparent difference between calling an extension method and the methods that are actually defined in a type.

最常見的擴充方法是 LINQLINQ 標準查詢運算子,這些運算子會將查詢功能新增至現有的 System.Collections.IEnumerableSystem.Collections.Generic.IEnumerable<T> 類型。The most common extension methods are the LINQLINQ standard query operators that add query functionality to the existing System.Collections.IEnumerable and System.Collections.Generic.IEnumerable<T> types. 若要使用標準查詢運算子,請先使用 using System.Linq 指示詞將它們帶入範圍內。To use the standard query operators, first bring them into scope with a using System.Linq directive. 接著,任何實作 IEnumerable<T> 的類型都會具有執行個體方法,如 GroupByOrderByAverage 等。Then any type that implements IEnumerable<T> appears to have instance methods such as GroupBy, OrderBy, Average, and so on. 如果在 IEnumerable<T> 類型 (如 List<T>Array) 的執行個體後面輸入「點」,就可以在 IntelliSense 陳述式完成時看到這些額外的方法。You can see these additional methods in IntelliSense statement completion when you type "dot" after an instance of an IEnumerable<T> type such as List<T> or Array.

下列範例將示範如何在整數陣列上呼叫標準查詢運算子 OrderBy 方法。The following example shows how to call the standard query operator OrderBy method on an array of integers. 括號括住的運算式就是 Lambda 運算式。The expression in parentheses is a lambda expression. 許多標準查詢運算子會將 Lambda 運算式當成參數,但是擴充方法不會強制這樣做。Many standard query operators take lambda expressions as parameters, but this is not a requirement for extension methods. 如需詳細資訊,請參閱 Lambda 運算式For more information, see Lambda Expressions.

class ExtensionMethods2    
{
    
    static void Main()
    {            
        int[] ints = { 10, 45, 15, 39, 21, 26 };
        var result = ints.OrderBy(g => g);
        foreach (var i in result)
        {
            System.Console.Write(i + " ");
        }           
    }        
}
//Output: 10 15 21 26 39 45

擴充方法會定義為靜態方法,但透過執行個體方法語法呼叫。Extension methods are defined as static methods but are called by using instance method syntax. 擴充方法的第一個參數會指定方法作業所在的類型,而且參數前面會加上 this 修飾詞。Their first parameter specifies which type the method operates on, and the parameter is preceded by the this modifier. 您必須使用 using 指示詞將命名空間明確匯入至原始程式碼,擴充方法才會進入範圍中。Extension methods are only in scope when you explicitly import the namespace into your source code with a using directive.

下列範例將示範針對 System.String 類別定義的擴充方法。The following example shows an extension method defined for the System.String class. 請注意,擴充方法是定義在非巢狀且非泛型的靜態類別內:Note that it is defined inside a non-nested, non-generic static class:

namespace ExtensionMethods
{
    public static class MyExtensions
    {
        public static int WordCount(this String str)
        {
            return str.Split(new char[] { ' ', '.', '?' }, 
                             StringSplitOptions.RemoveEmptyEntries).Length;
        }
    }   
}

使用這個 WordCount 指示詞就可以將 using 擴充方法帶入範圍中:The WordCount extension method can be brought into scope with this using directive:

using ExtensionMethods;  

而使用下列語法,就可以從應用程式中呼叫它:And it can be called from an application by using this syntax:

string s = "Hello Extension Methods";  
int i = s.WordCount();  

在您的程式碼中,可以利用執行個體方法語法來叫用擴充方法。In your code you invoke the extension method with instance method syntax. 不過,編譯器所產生的中繼語言 (IL) 會將您的程式碼轉譯為靜態方法上的呼叫。However, the intermediate language (IL) generated by the compiler translates your code into a call on the static method. 因此,實際上並未違反封裝 (Encapsulation) 的準則。Therefore, the principle of encapsulation is not really being violated. 事實上,擴充方法無法存取它們所擴充之類型中的私用變數。In fact, extension methods cannot access private variables in the type they are extending.

如需詳細資訊,請參閱如何:實作和呼叫自訂擴充方法For more information, see How to: Implement and Call a Custom Extension Method.

一般而言,您呼叫擴充方法的頻率將遠高於實作自己的方法。In general, you will probably be calling extension methods far more often than implementing your own. 因為擴充方法是使用執行個體方法語法進行呼叫,所以不需要任何特殊知識就可以從用戶端程式碼使用它們。Because extension methods are called by using instance method syntax, no special knowledge is required to use them from client code. 若要啟用特定類型的擴充方法,只要針對定義這些方法所在的命名空間加入 using 指示詞即可。To enable extension methods for a particular type, just add a using directive for the namespace in which the methods are defined. 例如,若要使用標準查詢運算子,請將下面這個 using 指示詞加入至程式碼:For example, to use the standard query operators, add this using directive to your code:

using System.Linq;  

(您可能也要加入對 System.Core.dll 的參考)。您將會注意到,標準查詢運算子現在出現在 IntelliSense 中,做為適用於大部分 IEnumerable<T> 類型的額外方法。(You may also have to add a reference to System.Core.dll.) You will notice that the standard query operators now appear in IntelliSense as additional methods available for most IEnumerable<T> types.

注意

雖然標準查詢運算子未針對 String 出現在 IntelliSense 中,但是您仍然可以使用它們。Although standard query operators do not appear in IntelliSense for String, they are still available.

在編譯時期繫結擴充方法Binding Extension Methods at Compile Time

您可以使用擴充方法來擴充類別或介面,但無法覆寫它們。You can use extension methods to extend a class or interface, but not to override them. 而且永遠不會呼叫擁有與介面或類別方法相同名稱和簽章的擴充方法。An extension method with the same name and signature as an interface or class method will never be called. 在編譯時期,擴充方法的優先順序一律低於類型本身中定義的執行個體方法。At compile time, extension methods always have lower priority than instance methods defined in the type itself. 換句話說,如果類型具有名為 Process(int i) 的方法,而您的擴充方法也具有相同的簽章,則編譯器一律會繫結至執行個體方法。In other words, if a type has a method named Process(int i), and you have an extension method with the same signature, the compiler will always bind to the instance method. 編譯器遇到方法引動過程時,會先在類型的執行個體方法中尋找相符項目。When the compiler encounters a method invocation, it first looks for a match in the type's instance methods. 如果找不到相符項目,則會搜尋任何針對該類型定義的擴充方法,並繫結至找到的第一個擴充方法。If no match is found, it will search for any extension methods that are defined for the type, and bind to the first extension method that it finds. 下列範例將示範編譯器如何判斷要繫結的擴充方法或執行個體方法。The following example demonstrates how the compiler determines which extension method or instance method to bind to.

範例Example

下列範例將示範 C# 編譯器遵循的規則,用以判斷要將方法呼叫繫結至類型上的執行個體方法,還是繫結至擴充方法。The following example demonstrates the rules that the C# compiler follows in determining whether to bind a method call to an instance method on the type, or to an extension method. 靜態類別 Extensions 包含針對任何實作 IMyInterface 之類型定義的擴充方法。The static class Extensions contains extension methods defined for any type that implements IMyInterface. 類別 ABC 都會實作這個介面。Classes A, B, and C all implement the interface.

因為 MethodB 擴充方法的名稱和簽章與這些類別已實作的方法完全相同,所以絕不會呼叫該方法。The MethodB extension method is never called because its name and signature exactly match methods already implemented by the classes.

當編譯器找不到具有相符簽章的執行個體方法時,就會繫結至相符的擴充方法 (如果有的話)。When the compiler cannot find an instance method with a matching signature, it will bind to a matching extension method if one exists.

// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
    using System;

    public interface IMyInterface
    {
        // Any class that implements IMyInterface must define a method
        // that matches the following signature.
        void MethodB();
    }
}


// Define extension methods for IMyInterface.
namespace Extensions
{
    using System;
    using DefineIMyInterface;

    // The following extension methods can be accessed by instances of any 
    // class that implements IMyInterface.
    public static class Extension
    {
        public static void MethodA(this IMyInterface myInterface, int i)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, int i)");
        }

        public static void MethodA(this IMyInterface myInterface, string s)
        {
            Console.WriteLine
                ("Extension.MethodA(this IMyInterface myInterface, string s)");
        }

        // This method is never called in ExtensionMethodsDemo1, because each 
        // of the three classes A, B, and C implements a method named MethodB
        // that has a matching signature.
        public static void MethodB(this IMyInterface myInterface)
        {
            Console.WriteLine
                ("Extension.MethodB(this IMyInterface myInterface)");
        }
    }
}


// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
    using System;
    using Extensions;
    using DefineIMyInterface;

    class A : IMyInterface
    {
        public void MethodB() { Console.WriteLine("A.MethodB()"); }
    }

    class B : IMyInterface
    {
        public void MethodB() { Console.WriteLine("B.MethodB()"); }
        public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
    }

    class C : IMyInterface
    {
        public void MethodB() { Console.WriteLine("C.MethodB()"); }
        public void MethodA(object obj)
        {
            Console.WriteLine("C.MethodA(object obj)");
        }
    }

    class ExtMethodDemo
    {
        static void Main(string[] args)
        {
            // Declare an instance of class A, class B, and class C.
            A a = new A();
            B b = new B();
            C c = new C();

            // For a, b, and c, call the following methods:
            //      -- MethodA with an int argument
            //      -- MethodA with a string argument
            //      -- MethodB with no argument.

            // A contains no MethodA, so each call to MethodA resolves to 
            // the extension method that has a matching signature.
            a.MethodA(1);           // Extension.MethodA(IMyInterface, int)
            a.MethodA("hello");     // Extension.MethodA(IMyInterface, string)

            // A has a method that matches the signature of the following call
            // to MethodB.
            a.MethodB();            // A.MethodB()

            // B has methods that match the signatures of the following
            // method calls.
            b.MethodA(1);           // B.MethodA(int)
            b.MethodB();            // B.MethodB()

            // B has no matching method for the following call, but 
            // class Extension does.
            b.MethodA("hello");     // Extension.MethodA(IMyInterface, string)

            // C contains an instance method that matches each of the following
            // method calls.
            c.MethodA(1);           // C.MethodA(object)
            c.MethodA("hello");     // C.MethodA(object)
            c.MethodB();            // C.MethodB()
        }
    }
}
/* Output:
    Extension.MethodA(this IMyInterface myInterface, int i)
    Extension.MethodA(this IMyInterface myInterface, string s)
    A.MethodB()
    B.MethodA(int i)
    B.MethodB()
    Extension.MethodA(this IMyInterface myInterface, string s)
    C.MethodA(object obj)
    C.MethodA(object obj)
    C.MethodB()
 */

一般方針General Guidelines

一般而言,建議您應謹慎地實作擴充方法,而且只有在必要時才實作。In general, we recommend that you implement extension methods sparingly and only when you have to. 當用戶端程式碼必須擴充現有的類型時,應該盡可能以建立衍生自現有類型的新類型來達成此目的。Whenever possible, client code that must extend an existing type should do so by creating a new type derived from the existing type. 如需詳細資訊,請參閱繼承For more information, see Inheritance.

使用擴充方法來擴充無法變更其原始程式碼的類型時,會有類型實作的變更導致擴充方法中斷的風險。When using an extension method to extend a type whose source code you cannot change, you run the risk that a change in the implementation of the type will cause your extension method to break.

如果您要實作所指定類型的擴充方法,請記住下列幾點:If you do implement extension methods for a given type, remember the following points:

  • 如果擴充方法的簽章與類型中定義的方法相同,則絕不會呼叫擴充方法。An extension method will never be called if it has the same signature as a method defined in the type.

  • 擴充方法是帶入命名空間層級的範圍。Extension methods are brought into scope at the namespace level. 例如,如果有多個靜態類別在名為 Extensions 的單一命名空間中包含擴充方法,則 using Extensions; 指示詞會將這些擴充方法全都帶入範圍中。For example, if you have multiple static classes that contain extension methods in a single namespace named Extensions, they will all be brought into scope by the using Extensions; directive.

針對實作的類別庫,您不應該使用擴充方法阻止組件的版本號碼遞增。For a class library that you implemented, you shouldn't use extension methods to avoid incrementing the version number of an assembly. 如果您要在擁有其原始程式碼的程式庫中加入重要功能,則應遵循組件版本控制的標準 .NET Framework 方針。If you want to add significant functionality to a library for which you own the source code, you should follow the standard .NET Framework guidelines for assembly versioning. 如需詳細資訊,請參閱組件版本控制For more information, see Assembly Versioning.

請參閱See Also

C# 程式設計指南C# Programming Guide
平行程式設計範例 (包括許多範例擴充方法)Parallel Programming Samples (these include many example extension methods)
Lambda 運算式Lambda Expressions
標準查詢運算子概觀Standard Query Operators Overview
Conversion rules for Instance parameters and their impact (執行個體參數的轉換規則與其影響)Conversion rules for Instance parameters and their impact
Extension methods Interoperability between languages (語言之間擴充方法的互通性)Extension methods Interoperability between languages
Extension methods and Curried Delegates (擴充方法和局部調用委派)Extension methods and Curried Delegates
Extension method Binding and Error reporting (擴充方法繫結和錯誤報告)Extension method Binding and Error reporting