拡張メソッド (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.

RemarksRemarks

拡張メソッドになるのは、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.

@No__t-0 属性は、Visual Basic ModuleSub、またはFunctionにのみ適用できます。The Extension attribute can only be applied to a Visual Basic Module, Sub, or Function. @No__t-0 または Structure に適用した場合、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

Print データ型の String 拡張を定義する例を次に示します。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 が含まれているモジュールをスコープの中に入れたら、引数を使用しない通常のインスタンス メソッド (ToUpper など) の場合と同じ要領でそのメソッドを呼び出すことができます。After 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 の拡張ですが、今回は 2 つのパラメーターを定義します。The next example, PrintAndPunctuate, is also an extension to String, this time defined with two parameters. 最初のパラメーター aString では、この拡張メソッドによって String を拡張することを指定します。The first parameter, aString, establishes that the extension method extends String. 2 番目のパラメーター 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(".")

PrintPrintAndPunctuate を定義して呼び出す例を次に示します。The 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. 拡張メソッドの Nothing は明示的にチェックできます。You 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 例外が発生します。これは、2 番目の 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. ただし、クラスが同じシグニチャの拡張メソッドを持つ 2 つのインターフェイスを実装する場合、どちらの拡張メソッドにもアクセスできません。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 型のパラメーターを 1 つ持つ Integer という名前のインスタンス メソッドが含まれています。In the following example, ExampleClass contains an instance method named ExampleMethod that has one parameter of type Integer. 拡張メソッド ExampleMethodExampleClass を拡張し、Long 型のパラメーターを 1 つ持ちます。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 の 2 回目の呼び出しでは、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

次は、2 つのメソッド間でパラメーターのデータ型が逆になっています。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. これは、arg1arg2Long へ拡大変換され、どちらの場合でも拡張メソッドよりインスタンス メソッドの方が優先されるためです。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

2 つの拡張メソッドのシグネチャが同じで、そのいずれもスコープに入っていてアクセスが可能な場合は、優先順位の高い方が呼び出されます。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