擴充方法可讓開發人員將自定義功能新增至已經定義的數據類型,而不需要建立新的衍生類型。 擴充方法可讓您撰寫可以呼叫的方法,就像是現有類型的實例方法一樣。
備註
擴充方法只能是 Sub
程式或 Function
程式。 您無法定義擴充屬性、欄位或事件。 所有擴充方法都必須以命名空間中的<Extension>
擴充屬性System.Runtime.CompilerServices標示,而且必須在Module中定義。 如果在模組外部定義擴充方法,Visual Basic 編譯程式會產生錯誤 BC36551:「擴充方法只能在模組中定義」。
擴充方法定義中的第一個參數會指定方法所擴充的數據類型。 執行 方法時,第一個參數會系結至叫用方法之數據類型的實例。
屬性 Extension
只能套用至 Visual Basic Module
、 Sub
或 Function
。 如果您將它套用至 Class
或 Structure
,Visual Basic 編譯程式會產生錯誤 BC36550,「'Extension' 屬性只能套用至 'Module'、'Sub' 或 'Function' 宣告」。
範例
下列範例會定義 Print
擴充為 String 數據類型。 方法會使用 Console.WriteLine
來顯示字串。 方法的 Print
參數確立aString
方法是擴充 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()
會引發MissingMemberException例外狀況,這個例外狀況就如同您刪除第二個PrintMe
擴充方法定義一樣。
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
包含一個名為 ExampleMethod
的實例方法,此方法有一個類型為 Integer
的參數。 擴充方法 ExampleMethod
擴充 ExampleClass
,並具有一個類型為 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
擴充方法,因為 arg1
是 Long
,而且僅與擴充方法中的 Long
參數相容。 第二次呼叫ExampleMethod
具有參數Integer
,而且它會呼叫實例方法arg2
。
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
程式代碼會呼叫實例方法兩次。 這是因為 arg1
和 arg2
都有對 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
屬性的情況比較簡單:如果擴充方法的名稱與其擴充之類別的屬性相同,則擴充方法不會顯示且無法存取。
擴充方法優先順序
當有相同簽章的兩個擴充方法位於範圍中且可存取時,將會叫用優先順序較高的擴充方法。 擴充方法的優先順序是以將方法帶入範圍的機制為基礎。 下列清單顯示優先順序階層,從最高到最低。
在目前模組內定義的擴充方法。
在目前命名空間或任何其父系的數據類型內定義的擴充方法,其子命名空間的優先順序高於父命名空間。
在目前檔案中所有類型的匯入內定義的擴充方法。
在目前檔案中任何命名空間匯入內定義的擴充方法。
任何專案層級類型匯入內定義的擴充方法。
任何專案層級命名空間匯入內定義的擴充方法。
如果優先權無法解決不明確性,您可以使用完整名稱來指定您要呼叫的方法。
Print
如果先前範例中的 方法是在名為 StringExtensions
的模組中定義,則完整名稱是 StringExtensions.Print(example)
,而不是 example.Print()
。