Методы расширения (Visual Basic)
Методы расширения позволяют разработчикам добавлять пользовательские функциональные возможности типам данных, которые уже определены, не создавая новый производный тип. Методы расширения делают возможным написание метода, который может вызываться, как если бы это был метод экземпляра существующего типа.
Заметки
Методом расширения может быть только процедура 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:
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(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 в качестве аргумента, отправляемого в первый параметр.
Если метод расширения вызывается для объекта, для которого установлено значение Nothing, метод расширения выполняется. Эта операция не применяется к обычным методам экземпляров. Можно явно проверять Nothing в методе расширения.
Типы, которые могут быть расширены
Можно определить метод расширения большинства типов, которые могут быть представлены в списке параметров Visual Basic, включая следующие:
Классы (ссылочные типы)
Структуры (типы значений)
Интерфейсы
Делегаты
Аргументы ByVal и ByRef
Параметры базового метода
Массивы
Поскольку первый параметр задает тип данных, который расширяет метод расширения, он необходим и не может быть необязательным. По этой причины параметры 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 присутствует аргумент 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 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
Ситуация выглядит проще со свойствами: если метод расширения имеет то же имя, что и свойство класса, который он расширяет, метод расширения невидим и недоступен.
Приоритет метода расширения
Когда два метода расширения, имеющие одинаковые сигнатуры, присутствуют в области действия и доступны, будет вызван метод с более высоким приоритетом. Приоритет метода расширения основан на механизме, используемом для переноса метода в область. В следующем списке показана иерархия приоритетов от самого высокого до самого низкого.
Методы расширения, определенные внутри текущего модуля.
Методы расширения, определенные внутри типов данных в текущем пространстве имен или одном из его родителей, с дочерними пространствами имен, имеющими более высокий приоритет, чем родительские пространства имен.
Методы расширения, определенные внутри любых импортов типа в текущем файле.
Методы расширения, определенные внутри любых импортов пространств имен в текущем файле.
Методы расширения, определенные внутри любых импортов типов на уровне проекта.
Методы расширения, определенные внутри любого импорта пространства имен на уровне проекта.
Если приоритет не помогает устранить неоднозначность, можно использовать полное проверенное имя для указания метода, который вы вызываете. Если метод Print в более раннем примере определен в модуле с именем StringExtensions, полным именем является StringExtensions.Print(example) вместо example.Print().
См. также
Ссылки
System.Runtime.CompilerServices
Методы расширения (Руководство по программированию в C#)
Основные понятия
Параметры и аргументы процедуры (Visual Basic)
Необязательные параметры (Visual Basic)