拡張メソッドを使用すると、開発者は、新しい派生型を作成せずに既に定義されているデータ型にカスタム機能を追加できます。 拡張メソッドを使用すると、既存の型のインスタンス メソッドであるかのように呼び出すことができるメソッドを記述できます。
注釈
拡張メソッドには、 Sub
プロシージャまたは Function
プロシージャのみを指定できます。 拡張プロパティ、フィールド、またはイベントを定義することはできません。 すべての拡張メソッドは、<Extension>
名前空間からSystem.Runtime.CompilerServices拡張属性でマークする必要があり、モジュールで定義する必要があります。 拡張メソッドがモジュールの外部で定義されている場合、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の拡張機能であり、今回は 2 つのパラメーターで定義されています。 最初のパラメーター aString
は、拡張メソッドが String拡張されることを確立します。 2 番目のパラメーター 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例外が発生します。2 番目の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
ベスト プラクティス
拡張メソッドは、既存の型を拡張する便利で強力な方法を提供します。 ただし、それらを正常に使用するには、いくつかの点を考慮する必要があります。 これらの考慮事項は、主にクラス ライブラリの作成者に適用されますが、拡張メソッドを使用するすべてのアプリケーションに影響する可能性があります。
ほとんどの場合、所有していない型に追加する拡張メソッドは、制御する型に追加される拡張メソッドよりも脆弱です。 所有していないクラスでは、拡張メソッドに干渉する可能性のある多くのことが発生する可能性があります。
呼び出し元のステートメント内の引数と互換性のあるシグネチャを持つアクセス可能なインスタンス メンバーが存在する場合、引数からパラメーターへの縮小変換は必要ありません。インスタンス メソッドは、任意の拡張メソッドを優先して使用されます。 そのため、ある時点で適切なインスタンス メソッドがクラスに追加されると、依存している既存の拡張メンバーにアクセスできなくなる可能性があります。
拡張メソッドの作成者は、他のプログラマが、元の拡張よりも優先される可能性がある競合する拡張メソッドを記述することを防ぐことはできません。
拡張メソッドを独自の名前空間に配置することで、堅牢性を向上させることができます。 ライブラリのコンシューマーは、名前空間を含めたり除外したり、ライブラリの残りの部分とは別に名前空間を選択したりできます。
特にインターフェイスまたはクラスを所有していない場合は、クラスを拡張するよりもインターフェイスを拡張する方が安全な場合があります。 インターフェイスの変更は、それを実装するすべてのクラスに影響します。 そのため、作成者がインターフェイスにメソッドを追加または変更する可能性が低くなる可能性があります。 ただし、クラスが同じシグネチャを持つ拡張メソッドを持つ 2 つのインターフェイスを実装する場合、どちらの拡張メソッドも表示されません。
可能な最も具体的な型を拡張します。 型の階層において、他の多くの型の派生元になる型を選択すると、インスタンスメソッドや他の拡張メソッドを導入する可能性の層が生じ、あなたのメソッドに干渉する可能性があります。
拡張メソッド、インスタンス メソッド、およびプロパティ
スコープ内のインスタンス メソッドに、呼び出し元のステートメントの引数と互換性のあるシグネチャがある場合、インスタンス メソッドは任意の拡張メソッドに優先して選択されます。 拡張メソッドの方が一致する場合でも、インスタンス メソッドの方が優先されます。 次の例では、ExampleClass
には、ExampleMethod
型のパラメーターが 1 つ含まれる Integer
という名前のインスタンス メソッドが含まれています。 拡張メソッドExampleMethod
ExampleClass
を拡張し、Long
型のパラメーターを 1 つ持ちます。
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
の 2 番目の呼び出しには、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
次に、2 つのメソッドのパラメーターのデータ型を逆にします。
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
のコードがインスタンス メソッドを 2 回呼び出します。 これは、 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
プロパティを使用すると、拡張メソッドが拡張クラスのプロパティと同じ名前を持つ場合、拡張メソッドは表示されず、アクセスできません。
拡張メソッドの優先順位
同じシグネチャを持つ 2 つの拡張メソッドがスコープ内にあり、アクセス可能な場合は、優先順位の高いメソッドが呼び出されます。 拡張メソッドの優先順位は、メソッドをスコープに取り込むためのメカニズムに基づいています。 次の一覧は、優先順位の階層を最高から最低の順に示しています。
現在のモジュール内で定義されている拡張メソッド。
現在の名前空間またはその親のいずれかのデータ型内で定義された拡張メソッド。子名前空間は親名前空間よりも優先順位が高くなります。
任意の型内で定義されている拡張メソッドは、現在のファイルにインポートされます。
現在のファイル内の名前空間インポート内で定義されている拡張メソッド。
プロジェクトレベルの型インポートで定義される拡張メソッド。
プロジェクト レベルの名前空間のインポート内で定義されている拡張メソッド。
優先順位によってあいまいさが解決されない場合は、完全修飾名を使用して、呼び出すメソッドを指定できます。 前の例のPrint
メソッドが StringExtensions
という名前のモジュールで定義されている場合、完全修飾名はStringExtensions.Print(example)
ではなくexample.Print()
されます。
こちらも参照ください
.NET