Поделиться через


Методы расширения (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, на этот раз определенный с двумя параметрами. Первый параметр, aString, указывает на то, что метод расширения распространяется на String. Второй параметр 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 исключение, то же исключение, которое вы бы увидели, если бы было удалено определение второго 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

Лучшие практики

Методы расширения предоставляют удобный и эффективный способ расширения существующего типа. Тем не менее, чтобы использовать их успешно, есть некоторые моменты, которые следует рассмотреть. Эти рекомендации применяются главным образом к авторам библиотек классов, но могут повлиять на любое приложение, использующее методы расширения.

В большинстве случаев методы расширения, добавляемые к типам, которыми вы не владеете, являются более уязвимыми, чем методы расширения, добавленные к типам, которые вы управляете. Ряд событий может происходить в классах, которыми вы не владеете, что может мешать вашим методам расширения.

  • Если существует какой-либо доступный элемент экземпляра с подписью, совместимой с аргументами в операторе вызова, и не требуются сужающие преобразования от аргумента к параметру, метод экземпляра будет использоваться предпочтительно перед любым методом расширения. Таким образом, если соответствующий метод экземпляра добавляется в класс в какой-то момент, существующий член расширения, который вы используете, может стать недоступным.

  • Автор метода расширения не может запретить другим программистам писать конфликтующие методы расширения, которые могут иметь приоритет над исходным расширением.

  • Вы можете повысить надежность, поставив методы расширения в собственное пространство имен. Затем потребители библиотеки могут включать пространство имен или исключать его или выбирать между пространствами имен отдельно от остальной части библиотеки.

  • Это может быть безопаснее для расширения интерфейсов, чем для расширения классов, особенно если вы не владеете интерфейсом или классом. Изменение интерфейса влияет на каждый класс, реализующий его. Таким образом, автор с меньшей вероятностью будет добавлять или изменять методы в интерфейсе. Однако если класс реализует два интерфейса с методами расширения с одной и той же сигнатурой, ни один метод расширения не отображается.

  • Расширьте наиболее конкретный тип. В иерархии типов, если вы выбираете тип, от которого производны многие другие типы, существуют многоуровневые возможности для внедрения методов экземпляра или других методов расширения, которые могут создать помехи для ваших.

Методы расширения, методы экземпляра и свойства

Если в методе экземпляра в области видимости есть сигнатура, совместимая с аргументами вызывающего выражения, метод экземпляра предпочитается любому методу расширения. Метод экземпляра имеет приоритет, даже если метод расширения лучше подходит. В следующем примере ExampleClass содержит метод экземпляра с именем ExampleMethod, который имеет один параметр типа Integer. Метод расширения ExampleMethod расширяет ExampleClass и имеет один параметр типа 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 в методе расширения. Второй вызов ExampleMethod имеет аргумент Integerarg2, и вызывает метод экземпляра.

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

Теперь измените типы данных параметров в двух методах:

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. Это связано с тем, что оба, 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

Ситуация проще с свойствами: если метод расширения имеет то же имя, что и свойство класса, которое он расширяет, метод расширения не отображается и не может быть доступен.

Приоритет метода расширения

Если два метода расширения с идентичными сигнатурами находятся в области видимости и доступны, вызовется тот, у которого выше приоритет. Приоритет метода расширения основан на механизме, используемом для привлечения метода в область. В следующем списке показана иерархия приоритета, от самого высокого до самого низкого.

  1. Методы расширения, определенные внутри текущего модуля.

  2. Методы расширения, определенные в рамках типов данных в текущем пространстве имен или любом из его родительских, где дочерние пространства имен имеют более высокий приоритет, чем родительские.

  3. Методы расширения, определенные внутри любого типа импорта в текущем файле.

  4. Методы расширения, определенные внутри любого импорта пространства имен в текущем файле.

  5. Методы расширения, определенные в рамках любых импортов типов на уровне проекта.

  6. Методы расширения, определенные внутри любого импорта пространства имен уровня проекта.

Если приоритет не устраняет неоднозначность, можно использовать полное имя для указания вызываемого метода. Если метод Print в предыдущем примере определен в модуле с именем StringExtensions, то полное имя – StringExtensions.Print(example) вместо example.Print().

См. также