扩展方法 (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.

备注Remarks

扩展方法只能是 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.

Extension 属性只能应用于 Visual Basic ModuleSubFunctionThe Extension attribute can only be applied to a Visual Basic Module, Sub, or Function. 如果将它应用于 ClassStructure,则 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

下面的示例定义 String 数据类型的 Print 扩展。The following example defines a Print extension to the String data type. 方法使用 Console.WriteLine 来显示字符串。The method uses Console.WriteLine to display a string. aStringPrint 方法的参数确定方法扩展了 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 的模块位于范围内后,可以调用方法,就像它是一个不采用任何参数的普通实例方法,如 ToUpperAfter 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的扩展,这一次使用两个参数定义。The next example, PrintAndPunctuate, is also an extension to String, this time defined with two parameters. aString的第一个参数确定扩展方法扩展 StringThe first parameter, aString, establishes that the extension method extends String. 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(".")

下面的示例显示定义和调用 PrintPrintAndPunctuateThe 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. 可以在扩展方法中显式检查 NothingYou 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 异常,这与第二个 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. 但是,如果某个类实现了两个具有相同签名的扩展方法的接口,则这两个扩展方法都不可见。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 的实例方法,该方法具有一个类型为 Integer的参数。In the following example, ExampleClass contains an instance method named ExampleMethod that has one parameter of type Integer. 扩展方法 ExampleMethod 扩展 ExampleClass,并且具有一个 Long类型的参数。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 的第二次调用具有 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

现在反转两种方法中参数的数据类型: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. 这是因为 arg1arg2 都具有与 Long的扩大转换,并且在这两种情况下,实例方法优先于扩展方法。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

如果两个具有相同签名的扩展方法处于范围内并且可访问,则将调用优先级较高的扩展方法。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