Методы расширения (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

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

См. также