擴充方法 (Visual Basic)Extension Methods (Visual Basic)

擴充方法可讓開發人員將自訂功能加入至已定義的資料類型,而不需要建立新的衍生類型。Extension methods enable developers to add custom functionality to data types that are already defined without creating a new derived type. 擴充方法可讓您撰寫方法,如同現有類型的實例方法一樣,可以呼叫它。Extension methods make it possible to write a method that can be called as if it were an instance method of the existing type.

備註Remarks

擴充方法只能是 Sub 程式或 Function 程式。An extension method can be only a Sub procedure or a Function procedure. 您不能定義擴充屬性、欄位或事件。You cannot define an extension property, field, or event. 所有的擴充方法都必須以 System.Runtime.CompilerServices 命名空間中的擴充屬性 <Extension> 標記,而且必須在模組中定義。All extension methods must be marked with the extension attribute <Extension> from the System.Runtime.CompilerServices namespace and must be defined in a Module. 如果在模組外部定義擴充方法,Visual Basic 編譯器會產生錯誤BC36551「擴充方法只能在模組中定義」。If an extension method is defined outside a module, the Visual Basic compiler generates error BC36551, "Extension methods can be defined only in modules".

擴充方法定義中的第一個參數會指定方法擴充的資料類型。The first parameter in an extension method definition specifies which data type the method extends. 當執行方法時,第一個參數會系結至叫用方法之資料類型的實例。When the method is run, the first parameter is bound to the instance of the data type that invokes the method.

Extension 屬性只能套用至 Visual Basic ModuleSubFunctionThe Extension attribute can only be applied to a Visual Basic Module, Sub, or Function. 如果您將它套用至 ClassStructure,Visual Basic 編譯器會產生錯誤BC36550,"Extension ' 屬性只能套用到 ' Module '、' Sub ' 或 ' Function ' 宣告。If you apply it to a Class or a Structure, the Visual Basic compiler generates error BC36550, "'Extension' attribute can be applied only to 'Module', 'Sub', or 'Function' declarations".

範例Example

下列範例會定義 String 資料類型的 Print 延伸模組。The following example defines a Print extension to the String data type. 方法會使用 Console.WriteLine 來顯示字串。The method uses Console.WriteLine to display a string. Print 方法的參數 aString,會建立方法擴充 String 類別。The parameter of the Print method, aString, establishes that the method extends the String class.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()> 
    Public Sub Print(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

End Module

請注意,擴充方法定義會標示 <Extension()>的擴充屬性。Notice that the extension method definition is marked with the extension attribute <Extension()>. 標記方法定義所在的模組是選擇性的,但每個擴充方法都必須標記為。Marking the module in which the method is defined is optional, but each extension method must be marked. 必須匯入 System.Runtime.CompilerServices,才能存取擴充屬性。System.Runtime.CompilerServices must be imported in order to access the extension attribute.

擴充方法只能在模組內宣告。Extension methods can be declared only within modules. 一般而言,在其中定義擴充方法的模組,與呼叫它的模組不同。Typically, the module in which an extension method is defined is not the same module as the one in which it is called. 相反地,會匯入包含擴充方法的模組(如果需要的話),使其成為範圍。Instead, the module that contains the extension method is imported, if it needs to be, to bring it into scope. 當包含 Print 的模組在範圍內時,可以呼叫方法,就像是不採用任何引數的一般實例方法,例如 ToUpperAfter the module that contains Print is in scope, the method can be called as if it were an ordinary instance method that takes no arguments, such as ToUpper:

Module Class1

    Sub Main()

        Dim example As String = "Hello"
        ' Call to extension method Print.
        example.Print()

        ' Call to instance method ToUpper.
        example.ToUpper()
        example.ToUpper.Print()

    End Sub

End Module

下一個範例 PrintAndPunctuate,也是 String的延伸,這次是使用兩個參數所定義。The next example, PrintAndPunctuate, is also an extension to String, this time defined with two parameters. 第一個參數(aString)會建立擴充方法擴充 StringThe first parameter, aString, establishes that the extension method extends String. 第二個參數(punc)的目的是要在呼叫方法時當做引數傳入的標點符號字串。The second parameter, punc, is intended to be a string of punctuation marks that is passed in as an argument when the method is called. 方法會顯示後面接著標點符號的字串。The method displays the string followed by the punctuation marks.

<Extension()> 
Public Sub PrintAndPunctuate(ByVal aString As String, 
                             ByVal punc As String)
    Console.WriteLine(aString & punc)
End Sub

方法的呼叫方式是傳送 punc的字串引數: example.PrintAndPunctuate(".")The method is called by sending in a string argument for punc: example.PrintAndPunctuate(".")

下列範例會顯示定義和呼叫 PrintPrintAndPunctuateThe following example shows Print and PrintAndPunctuate defined and called. System.Runtime.CompilerServices 會匯入定義模組中,以便啟用擴充屬性的存取。System.Runtime.CompilerServices is imported in the definition module in order to enable access to the extension attribute.

Imports System.Runtime.CompilerServices

Module StringExtensions

    <Extension()>
    Public Sub Print(aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()>
    Public Sub PrintAndPunctuate(aString As String, punc As String)
        Console.WriteLine(aString & punc)
    End Sub
End Module

接下來,擴充方法會帶入範圍並呼叫:Next, the extension methods are brought into scope and called:

Imports ConsoleApplication2.StringExtensions

Module Module1

    Sub Main()
        Dim example As String = "Example string"
        example.Print()

        example = "Hello"
        example.PrintAndPunctuate(".")
        example.PrintAndPunctuate("!!!!")
    End Sub
End Module

能夠執行這些或類似的擴充方法所需的一切,都是在範圍內。All that is required to be able to run these or similar extension methods is that they be in scope. 如果包含擴充方法的模組在範圍內,它會顯示在 IntelliSense 中,而且可以如同一般實例方法一樣呼叫。If the module that contains an extension method is in scope, it is visible in IntelliSense and can be called as if it were an ordinary instance method.

請注意,當叫用方法時,不會在中針對第一個參數傳送任何引數。Notice that when the methods are invoked, no argument is sent in for the first parameter. 先前方法定義中的參數 aString 會系結至 example,這是呼叫它們的 String 實例。Parameter aString in the previous method definitions is bound to example, the instance of String that calls them. 編譯器會使用 example 做為傳送至第一個參數的引數。The compiler will use example as the argument sent to the first parameter.

如果針對設定為 Nothing的物件呼叫擴充方法,則會執行擴充方法。If an extension method is called for an object that is set to Nothing, the extension method executes. 這並不適用于一般實例方法。This does not apply to ordinary instance methods. 您可以明確地檢查擴充方法中的 NothingYou can explicitly check for Nothing in the extension method.

可以擴充的類型Types that can be extended

您可以在可在 Visual Basic 參數清單中表示的大部分類型上定義擴充方法,包括下列各項:You can define an extension method on most types that can be represented in a Visual Basic parameter list, including the following:

  • 類別(參考型別)Classes (reference types)
  • 結構(實數值型別)Structures (value types)
  • 介面Interfaces
  • 委派Delegates
  • ByRef 和 ByVal 引數ByRef and ByVal arguments
  • 泛型方法參數Generic method parameters
  • 陣列Arrays

因為第一個參數指定擴充方法所擴充的資料類型,所以它是必要的,而且不能是選擇性的。Because the first parameter specifies the data type that the extension method extends, it is required and cannot be optional. 基於這個理由,Optional 參數和 ParamArray 參數不可以是參數清單中的第一個參數。For that reason, Optional parameters and ParamArray parameters cannot be the first parameter in the parameter list.

延伸方法不會被視為晚期繫結。Extension methods are not considered in late binding. 在下列範例中,語句 anObject.PrintMe() 引發 MissingMemberException 例外狀況,這是您在第二個 PrintMe 擴充方法定義已刪除時所看到的相同例外狀況。In the following example, the statement anObject.PrintMe() raises a MissingMemberException exception, the same exception you would see if the second PrintMe extension method definition were deleted.

Option Strict Off
Imports System.Runtime.CompilerServices

Module Module4

    Sub Main()
        Dim aString As String = "Initial value for aString"
        aString.PrintMe()

        Dim anObject As Object = "Initial value for anObject"
        ' The following statement causes a run-time error when Option
        ' Strict is off, and a compiler error when Option Strict is on.
        'anObject.PrintMe()
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal str As String)
        Console.WriteLine(str)
    End Sub

    <Extension()> 
    Public Sub PrintMe(ByVal obj As Object)
        Console.WriteLine(obj)
    End Sub

End Module

最佳作法Best practices

擴充方法提供了一個方便且功能強大的方式來擴充現有的類型。Extension methods provide a convenient and powerful way to extend an existing type. 不過,若要成功使用它們,有一些重點需要考慮。However, to use them successfully, there are some points to consider. 這些考慮主要適用于類別庫的作者,但它們可能會影響任何使用擴充方法的應用程式。These considerations apply mainly to authors of class libraries, but they might affect any application that uses extension methods.

最常見的情況是,您新增至您未擁有之類型的擴充方法,會比新增至您所控制之類型的擴充方法更容易受到攻擊。Most generally, extension methods that you add to types that you do not own are more vulnerable than extension methods added to types that you control. 您不擁有的類別可能會發生一些問題,這可能會干擾您的擴充方法。A number of things can occur in classes you do not own that can interfere with your extension methods.

  • 如果有任何可存取的實例成員存在,且其簽章與呼叫語句中的引數相容,而且引數至參數不需要任何縮小轉換,則實例方法將會在任何擴充方法的喜好設定中使用。If any accessible instance member exists that has a signature that is compatible with the arguments in the calling statement, with no narrowing conversions required from argument to parameter, the instance method will be used in preference to any extension method. 因此,如果在某個時間點將適當的實例方法加入至類別,則您依賴的現有延伸模組成員可能會變成無法存取。Therefore, if an appropriate instance method is added to a class at some point, an existing extension member that you rely on may become inaccessible.

  • 擴充方法的作者無法防止其他程式設計人員撰寫衝突的擴充方法,其優先順序可能高於原始的延伸模組。The author of an extension method cannot prevent other programmers from writing conflicting extension methods that may have precedence over the original extension.

  • 您可以藉由將擴充方法放在自己的命名空間中,來改善穩定性。You can improve robustness by putting extension methods in their own namespace. 您的程式庫取用者可以包含命名空間或將其排除,或在命名空間之間,與程式庫的其餘部分分開。Consumers of your library can then include a namespace or exclude it, or select among namespaces, separately from the rest of the library.

  • 擴充介面可能會比擴充類別更安全,特別是當您沒有擁有介面或類別時。It may be safer to extend interfaces than it is to extend classes, especially if you do not own the interface or class. 介面中的變更會影響每個執行它的類別。A change in an interface affects every class that implements it. 因此,作者可能比較不可能在介面中新增或變更方法。Therefore, the author may be less likely to add or change methods in an interface. 不過,如果類別所執行的兩個介面具有相同簽章的擴充方法,則不會顯示任何擴充方法。However, if a class implements two interfaces that have extension methods with the same signature, neither extension method is visible.

  • 擴充您可以的最特定類型。Extend the most specific type you can. 在類型的階層架構中,如果您選取衍生許多其他類型的類型,可能會有一些層級的導入實例方法或其他可能會干擾您的的擴充方法。In a hierarchy of types, if you select a type from which many other types are derived, there are layers of possibilities for the introduction of instance methods or other extension methods that might interfere with yours.

擴充方法、實例方法和屬性Extension methods, instance methods, and properties

當範圍內的實例方法具有與呼叫語句的引數相容的簽章時,會將實例方法選擇為任何擴充方法的喜好設定。When an in-scope instance method has a signature that is compatible with the arguments of a calling statement, the instance method is chosen in preference to any extension method. 即使擴充方法更符合,實例方法仍具有優先權。The instance method has precedence even if the extension method is a better match. 在下列範例中,ExampleClass 包含名為 ExampleMethod 的實例方法,其中有一個 Integer類型的參數。In the following example, ExampleClass contains an instance method named ExampleMethod that has one parameter of type Integer. 擴充方法 ExampleMethod 延伸 ExampleClass,而且具有 Long類型的一個參數。Extension method ExampleMethod extends ExampleClass, and has one parameter of type Long.

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Integer)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Long)
    Console.WriteLine("Extension method")
End Sub

第一次呼叫下列程式碼中的 ExampleMethod 時,會呼叫擴充方法,因為 arg1Long,而且僅與擴充方法中的 Long 參數相容。The first call to ExampleMethod in the following code calls the extension method, because arg1 is Long and is compatible only with the Long parameter in the extension method. ExampleMethod 的第二個呼叫具有 Integer 引數 arg2,而且它會呼叫實例方法。The second call to ExampleMethod has an Integer argument, arg2, and it calls the instance method.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the extension method.
    example.exampleMethod(arg1)
    ' The following statement calls the instance method.
    example.exampleMethod(arg2)
End Sub

現在,請在兩個方法中反轉參數的資料類型:Now reverse the data types of the parameters in the two methods:

Class ExampleClass
    ' Define an instance method named ExampleMethod.
    Public Sub ExampleMethod(ByVal m As Long)
        Console.WriteLine("Instance method")
    End Sub
End Class

<Extension()> 
Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal n As Integer)
    Console.WriteLine("Extension method")
End Sub

這次 Main 中的程式碼會同時呼叫實例方法。This time the code in Main calls the instance method both times. 這是因為 arg1arg2 都有 Long的擴輾轉換,而在這兩種情況下,實例方法的優先順序高於擴充方法。This is because both arg1 and arg2 have a widening conversion to Long, and the instance method takes precedence over the extension method in both cases.

Sub Main()
    Dim example As New ExampleClass
    Dim arg1 As Long = 10
    Dim arg2 As Integer = 5

    ' The following statement calls the instance method.
    example.ExampleMethod(arg1)
    ' The following statement calls the instance method.
    example.ExampleMethod(arg2)
End Sub

因此,擴充方法無法取代現有的實例方法。Therefore, an extension method cannot replace an existing instance method. 不過,當擴充方法的名稱與實例方法相同,但是簽章不衝突時,可以存取這兩種方法。However, when an extension method has the same name as an instance method but the signatures do not conflict, both methods can be accessed. 例如,如果類別 ExampleClass 包含名為 ExampleMethod 但不採用任何引數的方法,則會允許具有相同名稱但具有不同簽章的擴充方法,如下列程式碼所示。For example, if class ExampleClass contains a method named ExampleMethod that takes no arguments, extension methods with the same name but different signatures are permitted, as shown in the following code.

Imports System.Runtime.CompilerServices

Module Module3

    Sub Main()
        Dim ex As New ExampleClass
        ' The following statement calls the extension method.
        ex.ExampleMethod("Extension method")
        ' The following statement calls the instance method.
        ex.ExampleMethod()
    End Sub

    Class ExampleClass
        ' Define an instance method named ExampleMethod.
        Public Sub ExampleMethod()
            Console.WriteLine("Instance method")
        End Sub
    End Class

    <Extension()> 
    Sub ExampleMethod(ByVal ec As ExampleClass, 
                  ByVal stringParameter As String)
        Console.WriteLine(stringParameter)
    End Sub

End Module

這段程式碼的輸出如下所示:The output from this code is as follows:

Extension method
Instance method

屬性的情況較簡單:如果擴充方法的名稱與它所擴充之類別的屬性相同,則不會顯示擴充方法,而且無法存取。The situation is simpler with properties: if an extension method has the same name as a property of the class it extends, the extension method is not visible and cannot be accessed.

擴充方法優先順序Extension method precedence

當具有相同簽章的兩個擴充方法在範圍內且可供存取時,將會叫用具有較高優先順序的延伸模組。When two extension methods that have identical signatures are in scope and accessible, the one with higher precedence will be invoked. 擴充方法的優先順序是以用來將方法帶入範圍的機制為基礎。An extension method's precedence is based on the mechanism used to bring the method into scope. 下列清單顯示優先順序階層,從最高到最低。The following list shows the precedence hierarchy, from highest to lowest.

  1. 在目前模組內定義的擴充方法。Extension methods defined inside the current module.

  2. 在目前命名空間中的資料類型或其任何一個父系內定義的擴充方法,其子命名空間的優先順序高於父命名空間。Extension methods defined inside data types in the current namespace or any one of its parents, with child namespaces having higher precedence than parent namespaces.

  3. 在目前檔案中的任何類型匯入內定義的擴充方法。Extension methods defined inside any type imports in the current file.

  4. 在目前檔案中的任何命名空間匯入內定義的擴充方法。Extension methods defined inside any namespace imports in the current file.

  5. 定義于任何專案層級類型匯入內的擴充方法。Extension methods defined inside any project-level type imports.

  6. 在任何專案層級命名空間匯入中定義的擴充方法。Extension methods defined inside any project-level namespace imports.

如果優先順序無法解決不明確的問題,您可以使用完整名稱來指定您要呼叫的方法。If precedence does not resolve the ambiguity, you can use the fully qualified name to specify the method that you are calling. 如果先前範例中的 Print 方法是在名為 StringExtensions的模組中定義,則會 StringExtensions.Print(example) 完整名稱,而不是 example.Print()If the Print method in the earlier example is defined in a module named StringExtensions, the fully qualified name is StringExtensions.Print(example) instead of example.Print().

另請參閱See also