Методы расширения (Visual Basic)
Методы расширения позволяют разработчикам добавлять пользовательские функции в типы данных, которые уже определены без создания нового производного типа. Методы расширения позволяют написать метод, который можно вызвать, как если бы он был методом экземпляра существующего типа.
Замечания
Метод расширения может быть только процедурой Sub
или процедурой Function
. Нельзя определить свойство расширения, поле или событие. Все методы расширения должны быть помечены атрибутом <Extension>
расширения из System.Runtime.CompilerServices пространства имен и должны быть определены в модуле. Если метод расширения определен за пределами модуля, компилятор Visual Basic создает ошибку BC36551" "Методы расширения можно определить только в модулях".
Первый параметр в определении метода расширения указывает, какой тип данных расширяется. При запуске метода первый параметр привязан к экземпляру типа данных, вызывающего метод.
Атрибут Extension
может применяться только к Visual Basic Module
Sub
или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
аргумента 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()
него.