擴充方法 (Visual Basic)

擴充方法可讓開發人員將自訂功能新增至已經定義的資料類型,而不需要建立新的衍生類型。 擴充方法可讓您撰寫可以呼叫的方法,就像它是現有類型的實例方法一樣。

備註

擴充方法只能是 Sub 程式或 Function 程式。 您無法定義擴充屬性、欄位或事件。 所有擴充方法都必須以命名空間中的 System.Runtime.CompilerServices 擴充屬性 <Extension> 標示,而且必須在模組中定義。 如果在模組外部定義擴充方法,Visual Basic編譯器會產生錯誤BC36551:「擴充方法只能在模組中定義」。

擴充方法定義中的第一個參數會指定方法所擴充的資料類型。 執行 方法時,第一個參數會系結至叫用方法之資料類型的實例。

屬性 Extension 只能套用至Visual Basic ModuleSubFunction 。 如果您將它套用至 ClassStructure ,Visual Basic編譯器會產生錯誤BC36550,「'Extension' 屬性只能套用至 'Module'、'Sub' 或 'Function' 宣告。

範例

下列範例會 Print 定義資料類型的 String 延伸模組。 方法會使用 Console.WriteLine 來顯示字串。 方法 aStringPrint 參數會建立 方法擴充 String 類別。

Imports System.Runtime.CompilerServices

Module StringExtensions

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

End Module

請注意,擴充方法定義會以擴充屬性 <Extension()> 標示。 標記定義方法的模組是選擇性的,但必須標記每個擴充方法。 System.Runtime.CompilerServices 必須匯入才能存取擴充屬性。

擴充方法只能在模組內宣告。 一般而言,定義擴充方法的模組與呼叫擴充方法的模組不同。 相反地,如果需要的話,會匯入包含擴充方法的模組,使其進入範圍。 在包含 的 Print 模組位於範圍內之後,可以呼叫 方法,就像是一般實例方法,而不需要引數,例如 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 延伸模組,這次是以兩個參數定義。 第一個參數 aString 會建立擴充方法擴充 String 。 第二個參數 punc 是做為標點符號的字串,在呼叫 方法時會當做引數傳入。 方法會顯示字串,後面接著標點符號。

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

在 的字串引數 punc 中傳送 ,以呼叫 方法: example.PrintAndPunctuate(".")

下列範例示範 Print 並定義和 PrintAndPunctuate 呼叫。 System.Runtime.CompilerServices 會匯入定義模組,以啟用擴充屬性的存取權。

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

接下來,擴充方法會帶入範圍並呼叫:

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

必須能夠執行這些或類似的擴充方法,就是它們位於範圍內。 如果包含擴充方法的模組位於範圍內,它會顯示在 IntelliSense 中,而且可以呼叫,就像是一般實例方法一樣。

請注意,叫用方法時,不會針對第一個參數傳送引數。 先前方法定義中的參數 aString 會系結至 example ,該實例 String 會呼叫它們。 編譯器會使用 example 作為傳送至第一個參數的引數。

如果針對設定 Nothing 為 的物件呼叫擴充方法,則擴充方法會執行。 這不適用於一般實例方法。 您可以在擴充方法中明確檢查 Nothing

可以擴充的類型

您可以在大部分類型上定義擴充方法,這些類型可以在Visual Basic參數清單中表示,包括下列專案:

  • 類別 (參考型別)
  • 結構 (實值型別)
  • 介面
  • 委派
  • ByRef 和 ByVal 引數
  • 泛型方法參數
  • 陣列

因為第一個參數會指定擴充方法擴充的資料類型,所以它是必要的,而且不能是選擇性的。 基於這個理由, Optional 參數和 ParamArray 參數不能是參數清單中的第一個參數。

擴充方法不會在晚期繫結中考慮。 在下列範例中,語句 anObject.PrintMe() 會引發例外狀況,您會看到第二 PrintMeMissingMemberException 擴充方法定義是否已刪除的相同例外狀況。

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

最佳作法

擴充方法提供方便且功能強大的方法來擴充現有的類型。 不過,若要順利使用它們,需要考慮一些重點。 這些考慮主要適用于類別庫的作者,但可能會影響任何使用擴充方法的應用程式。

一般而言,您新增至您不擁有之類型的擴充方法比新增至您所控制類型的擴充方法更容易攻擊。 您不擁有的類別中可能會發生許多可能會干擾擴充方法的專案。

  • 如果有任何可存取的實例成員存在,且簽章與呼叫語句中的引數相容,而且不需要將引數轉換成參數,則實例方法將用於任何擴充方法的喜好設定。 因此,如果在某個時間點將適當的實例方法新增至類別,您依賴的現有擴充成員可能會變成無法存取。

  • 擴充方法的作者無法防止其他程式設計人員撰寫可能優先于原始延伸模組的衝突擴充方法。

  • 您可以將擴充方法放在自己的命名空間中,以改善健全性。 然後,程式庫的取用者可以包含命名空間或將其排除,或選取命名空間,與程式庫的其餘部分分開。

  • 擴充介面可能比擴充類別更安全,特別是如果您沒有擁有介面或類別。 介面中的變更會影響實作它的每個類別。 因此,作者可能較不太可能在介面中新增或變更方法。 不過,如果類別實作兩個具有相同簽章擴充方法的介面,則看不到兩個擴充方法。

  • 擴充您可以擴充最特定的類型。 在型別階層中,如果您選取衍生自許多其他類型的類型,可能會有一層可能會引進實例方法或其他可能會干擾您的擴充方法。

擴充方法、實例方法和屬性

當範圍中的實例方法具有與呼叫語句引數相容的簽章時,會偏好使用任何擴充方法的實例方法。 即使擴充方法較符合,實例方法仍具有優先順序。 在下列範例中, ExampleClass 包含名為 ExampleMethodInteger 實例方法,其類型為 的一個參數。 擴充方法 ExampleMethodExampleClass 擴充 ,而且具有 類型的 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 擴充方法中的 參數相容。 的第二個呼叫 ExampleMethod 具有 引數, arg2 它會呼叫 實例 Integer 方法。

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

現在,反轉兩種方法中參數的資料類型:

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 程式碼會呼叫 實例方法兩次。 這是因為 和 arg2 都有 arg1 擴大轉換至 Long ,而且實例方法在這兩種情況下的擴充方法優先于擴充方法。

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

因此,擴充方法無法取代現有的實例方法。 不過,當擴充方法的名稱與實例方法相同,但簽章不衝突時,可以存取這兩種方法。 例如,如果 類別 ExampleClass 包含一個名為 ExampleMethod 且不採用引數的方法,則允許具有相同名稱但不同簽章的擴充方法,如下列程式碼所示。

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

此程式碼的輸出如下所示:

Extension method
Instance method

使用屬性會比較簡單:如果擴充方法的名稱與所擴充之類別的屬性名稱相同,則無法看見擴充方法,而且無法存取。

擴充方法優先順序

當具有相同簽章的兩個擴充方法位於範圍內且可存取時,將會叫用優先順序較高的擴充方法。 擴充方法的優先順序是以用來將方法帶入範圍的機制為基礎。 下列清單顯示優先順序階層,從最高到最低。

  1. 目前模組內定義的擴充方法。

  2. 目前命名空間或任何父系資料類型內定義的擴充方法,其子命名空間的優先順序高於父命名空間。

  3. 在目前檔案中任何類型匯入內定義的擴充方法。

  4. 定義于目前檔案中任何命名空間匯入的擴充方法。

  5. 在任何專案層級類型匯入內定義的擴充方法。

  6. 在任何專案層級命名空間匯入內定義的擴充方法。

如果優先順序未解析模棱兩可,您可以使用完整名稱來指定您呼叫的方法。 Print如果先前範例中的 方法是在名為 StringExtensionsexample.Print() 模組中定義,則完整名稱是 StringExtensions.Print(example) ,而不是 。

另請參閱