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


Вариативность в делегатах (Visual Basic)

Платформа .NET Framework 3.5 представила поддержку несоответствия подписей методов с типами делегатов во всех делегатах в C# и Visual Basic. Это означает, что можно назначить делегатам не только методы, имеющие соответствующие сигнатуры, но и методы, возвращающие более производные типы (ковариантность) или принимающие параметры, имеющие менее производные типы (контравариантность), чем указанные типом делегата. Это включает как универсальные, так и не универсальные делегаты.

Например, рассмотрим следующий код, имеющий два класса и два делегата: универсальный и не универсальный.

Public Class First
End Class

Public Class Second
    Inherits First
End Class

Public Delegate Function SampleDelegate(ByVal a As Second) As First
Public Delegate Function SampleGenericDelegate(Of A, R)(ByVal a As A) As R

При создании делегатов SampleDelegate или SampleDelegate(Of A, R) типов можно назначить один из следующих методов этим делегатам.

' Matching signature.
Public Shared Function ASecondRFirst(
    ByVal second As Second) As First
    Return New First()
End Function

' The return type is more derived.
Public Shared Function ASecondRSecond(
    ByVal second As Second) As Second
    Return New Second()
End Function

' The argument type is less derived.
Public Shared Function AFirstRFirst(
    ByVal first As First) As First
    Return New First()
End Function

' The return type is more derived
' and the argument type is less derived.
Public Shared Function AFirstRSecond(
    ByVal first As First) As Second
    Return New Second()
End Function

В следующем примере кода показано неявное преобразование между сигнатурой метода и типом делегата.

' Assigning a method with a matching signature
' to a non-generic delegate. No conversion is necessary.
Dim dNonGeneric As SampleDelegate = AddressOf ASecondRFirst
' Assigning a method with a more derived return type
' and less derived argument type to a non-generic delegate.
' The implicit conversion is used.
Dim dNonGenericConversion As SampleDelegate = AddressOf AFirstRSecond

' Assigning a method with a matching signature to a generic delegate.
' No conversion is necessary.
Dim dGeneric As SampleGenericDelegate(Of Second, First) = AddressOf ASecondRFirst
' Assigning a method with a more derived return type
' and less derived argument type to a generic delegate.
' The implicit conversion is used.
Dim dGenericConversion As SampleGenericDelegate(Of Second, First) = AddressOf AFirstRSecond

Дополнительные примеры см. в статье "Использование вариативности в делегатах (Visual Basic)" и "Использование вариативности для Func и Action Generic Delegates (Visual Basic)".

Дисперсия в параметрах универсального типа

В .NET Framework 4 и более поздних версиях доступно неявное преобразование между делегатами, что позволяет назначать универсальные делегаты с различными типами, заданными параметрами универсального типа, друг другу, если типы наследуются друг от друга, как требуется для вариативности.

Чтобы включить неявное преобразование, необходимо явно объявить универсальные параметры в делегате как ковариантные или контравариантные с помощью ключевого слова in или out.

В следующем примере кода показано, как создать делегат с ковариантным параметром универсального типа.

' Type T is declared covariant by using the out keyword.
Public Delegate Function SampleGenericDelegate(Of Out T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "
    ' You can assign delegates to each other,
    ' because the type T is declared covariant.
    Dim dObject As SampleGenericDelegate(Of Object) = dString
End Sub

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

В следующем примере кода невозможно явно преобразовать SampleGenericDelegate(Of String) в SampleGenericDelegate(Of Object), хотя String является наследником Object. Эту проблему можно устранить, пометив универсальный параметр T ключевым словом out .

Public Delegate Function SampleGenericDelegate(Of T)() As T
Sub Test()
    Dim dString As SampleGenericDelegate(Of String) = Function() " "

    ' You can assign the dObject delegate
    ' to the same lambda expression as dString delegate
    ' because of the variance support for
    ' matching method signatures with delegate types.
    Dim dObject As SampleGenericDelegate(Of Object) = Function() " "

    ' The following statement generates a compiler error
    ' because the generic type T is not marked as covariant.
    ' Dim dObject As SampleGenericDelegate(Of Object) = dString

End Sub

Универсальные делегаты с параметрами типа variant в .NET Framework

Платформа .NET Framework 4 ввела поддержку для вариативности параметров обобщённого типа в нескольких уже существующих обобщённых делегатах.

Дополнительные сведения и примеры см. в разделе «Использование вариативности для обобщённых делегатов Func и Action (Visual Basic)».

Объявление параметров типа ковариантных и контрвариантных в обобщённых делегатах

Если универсальный делегат имеет ковариантные или контравариантные параметры универсального типа, он может называться вариантным универсальным делегатом.

Параметр универсального типа можно объявить ковариантным в универсальном делегате с помощью ключевого out слова. Ковариантный тип можно использовать только в качестве типа возвращаемого метода, а не в качестве типа аргументов метода. В следующем примере кода показано, как объявить ковариантный универсальный делегат.

Public Delegate Function DCovariant(Of Out R)() As R

Параметр универсального типа можно объявить контравариантным в универсальном делегате с помощью ключевого слова in. Контравариантный тип можно использовать только в качестве типа аргументов метода, а не в качестве возвращаемого типа метода. В следующем примере кода показано, как объявить контравариантный универсальный делегат.

Public Delegate Sub DContravariant(Of In A)(ByVal a As A)

Это важно

ByRef параметры в Visual Basic не могут быть помечены как вариант.

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

Public Delegate Function DVariant(Of In A, Out R)(ByVal a As A) As R

Создание экземпляров и вызов вариантных универсальных делегатов.

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

Dim dvariant As DVariant(Of String, String) = Function(str) str + " "
dvariant("test")

Объединение обобщённых делегатов с вариативностью

Не следует объединять вариативные делегаты. Метод Combine не поддерживает преобразование вариантов делегатов и ожидает, что делегаты будут иметь точно тот же тип. Это может привести к исключению во время выполнения при объединении делегатов с помощью метода Combine (в C# и Visual Basic) или оператора + (в C#), как показано в следующем примере кода.

Dim actObj As Action(Of Object) = Sub(x) Console.WriteLine("object: {0}", x)
Dim actStr As Action(Of String) = Sub(x) Console.WriteLine("string: {0}", x)

' The following statement throws an exception at run time.
' Dim actCombine = [Delegate].Combine(actStr, actObj)

Дисперсия в параметрах универсального типа для типов значений и ссылочных типов

Вариативность параметров универсального типа поддерживается только для ссылочных типов. Например, DVariant(Of Int)нельзя неявно преобразовать в DVariant(Of Object) или DVariant(Of Long), так как целое число является типом значения.

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

' The type T is covariant.
Public Delegate Function DVariant(Of Out T)() As T
' The type T is invariant.
Public Delegate Function DInvariant(Of T)() As T
Sub Test()
    Dim i As Integer = 0
    Dim dInt As DInvariant(Of Integer) = Function() i
    Dim dVariantInt As DVariant(Of Integer) = Function() i

    ' All of the following statements generate a compiler error
    ' because type variance in generic parameters is not supported
    ' for value types, even if generic type parameters are declared variant.
    ' Dim dObject As DInvariant(Of Object) = dInt
    ' Dim dLong As DInvariant(Of Long) = dInt
    ' Dim dVariantObject As DInvariant(Of Object) = dInt
    ' Dim dVariantLong As DInvariant(Of Long) = dInt
End Sub

Упрощенное преобразование делегатов в Visual Basic

Упрощённое преобразование делегатов обеспечивает большую гибкость при сопоставлении сигнатур методов с типами делегатов. Например, он позволяет опустить спецификации параметров и опустить возвращаемые значения функции при назначении метода делегату. Дополнительные сведения см. в разделе "Упрощенное преобразование делегатов".

См. также