次の方法で共有


拡張メソッド (Visual Basic)

拡張メソッドを使用すると、開発者は、新しい派生型を作成せずに既に定義されているデータ型にカスタム機能を追加できます。 拡張メソッドを使用すると、既存の型のインスタンス メソッドであるかのように呼び出すことができるメソッドを記述できます。

注釈

拡張メソッドには、 Sub プロシージャまたは Function プロシージャのみを指定できます。 拡張プロパティ、フィールド、またはイベントを定義することはできません。 すべての拡張メソッドは、<Extension>名前空間からSystem.Runtime.CompilerServices拡張属性でマークする必要があり、モジュールで定義する必要があります。 拡張メソッドがモジュールの外部で定義されている場合、Visual Basic コンパイラはエラー BC36551 "拡張メソッドはモジュールでのみ定義できます" を生成します。

拡張メソッド定義の最初のパラメーターは、メソッドが拡張するデータ型を指定します。 メソッドを実行すると、最初のパラメーターは、メソッドを呼び出すデータ型のインスタンスにバインドされます。

Extension属性は、Visual Basic ModuleSub、または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(".")

次の例は、 PrintPrintAndPunctuate 定義および呼び出しを示しています。 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 という名前のインスタンス メソッドが含まれています。 拡張メソッドExampleMethodExampleClassを拡張し、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 を呼び出すと、拡張メソッドが呼び出されます。 arg1Long され、拡張メソッドの 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 回呼び出します。 これは、 arg1arg2 の両方に 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 つの拡張メソッドがスコープ内にあり、アクセス可能な場合は、優先順位の高いメソッドが呼び出されます。 拡張メソッドの優先順位は、メソッドをスコープに取り込むためのメカニズムに基づいています。 次の一覧は、優先順位の階層を最高から最低の順に示しています。

  1. 現在のモジュール内で定義されている拡張メソッド。

  2. 現在の名前空間またはその親のいずれかのデータ型内で定義された拡張メソッド。子名前空間は親名前空間よりも優先順位が高くなります。

  3. 任意の型内で定義されている拡張メソッドは、現在のファイルにインポートされます。

  4. 現在のファイル内の名前空間インポート内で定義されている拡張メソッド。

  5. プロジェクトレベルの型インポートで定義される拡張メソッド。

  6. プロジェクト レベルの名前空間のインポート内で定義されている拡張メソッド。

優先順位によってあいまいさが解決されない場合は、完全修飾名を使用して、呼び出すメソッドを指定できます。 前の例のPrint メソッドが StringExtensions という名前のモジュールで定義されている場合、完全修飾名はStringExtensions.Print(example)ではなくexample.Print()されます。

こちらも参照ください