Методы расширения (Visual Basic)

Обновлен: Ноябрь 2007

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

Примечания

Методом расширения может быть только процедура Sub или Function. Нельзя определить свойство расширения, поле или событие. Все методы расширения должен быть помечены атрибутом расширения <Extension()> из пространства имен System.Runtime.CompilerServices.

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

Пример

Описание

В следующем примере определяется расширение 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:

Imports ConsoleApplication2.StringExtensions

Module Module1

    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(ByVal aString As String)
        Console.WriteLine(aString)
    End Sub

    <Extension()> _
    Public Sub PrintAndPunctuate(ByVal aString As String, _
                                 ByVal 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 в качестве аргумента, отправляемого в первый параметр.

Типы, которые могут быть расширены

Можно определить метод расширения большинства типов, которые могут быть представлены в списке параметров Visual Basic, включая следующие:

  • Классы (ссылочные типы)

  • Структуры (типы значений)

  • Интерфейсы

  • Делегаты

  • Аргументы ByVal и ByRef

  • Параметры базового метода

  • Массивы

Поскольку первый параметр задает тип данных, который расширяет метод расширения, он необходим и не может быть необязательным. По этой причины параметры Optional и ParamArray не могут быть первым параметром в списке параметров.

Лучшие методики

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

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

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

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

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

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

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

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

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

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 ConsoleApplication2.ExtensionExample
Module Module1

    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

End Module
Imports System.Runtime.CompilerServices

' Define an extension method named ExampleMethod.
Module ExtensionExample
    <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().

См. также

Задачи

Практическое руководство. Объявление необязательных параметров процедуры

Основные понятия

Применение атрибутов

Параметры и аргументы процедуры

Необязательные параметры

Массивы параметров

Общие сведения об атрибутах в Visual Basic

Область видимости в Visual Basic

Ссылки

System.Runtime.CompilerServices

Оператор Module

ExtensionAttribute