Методы расширения (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. Все методы расширения должны быть помечены атрибутом расширения <Extension> из пространства имен System.Runtime.CompilerServices и должны быть определены в модуле.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 Module, Subили Function.The Extension attribute can only be applied to a Visual Basic Module, Sub, or Function. Если применить его к Class или Structure, компилятор Visual Basic создает ошибку BC36550, атрибут "Extension" может применяться только к объявлениям "Module", "" или "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, на этот раз определено с двумя параметрами.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. Второй параметр, 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(".")

В следующем примере показаны Print и PrintAndPunctuate, определенные и вызванные.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 и ByValByRef 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 в следующем коде вызывает метод расширения, так как arg1 является Long и совместим только с параметром 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. Это происходит потому, что как arg1, так arg2 имеют расширяющее преобразование в 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