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

В платформе .NET Framework 3.5 и Visual Studio 2008 появилась поддержка вариативности при сопоставлении сигнатур методов с типами делегатов во всех делегатах в 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
public class First { }
public class Second : First { }
public delegate First SampleDelegate(Second a);
public delegate R SampleGenericDelegate<A, R>(A a);

При создании делегатов типов SampleDelegate или SampleGenericDelegate<A, R> (SampleDelegate(Of A, R) в Visual Basic) им можно назначить любой из следующих методов.

' 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
// Matching signature. 
public static First ASecondRFirst(Second first)
{ return new First(); }

// The return type is more derived. 
public static Second ASecondRSecond(Second second)
{ return new Second(); }

// The argument type is less derived. 
public static First AFirstRFirst(First first)
{ return new First(); }

// The return type is more derived  
// and the argument type is less derived. 
public static Second AFirstRSecond(First first)
{ return new Second(); }

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

' 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
// Assigning a method with a matching signature  
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = 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.
SampleDelegate dNonGenericConversion = AFirstRSecond;

// Assigning a method with a matching signature to a generic delegate. 
// No conversion is necessary.
SampleGenericDelegate<Second, First> dGeneric = ASecondRFirst;
// Assigning a method with a more derived return type  
// and less derived argument type to a generic delegate. 
// The implicit conversion is used.
SampleGenericDelegate<Second, First> dGenericConversion = AFirstRSecond;

Дополнительные примеры см. в разделах Использование вариативности в делегатах (C# и Visual Basic) и Использование вариативности в универсальных методах-делегатах Func и Action (C# и 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
// Type T is declared covariant by using the out keyword. 
public delegate T SampleGenericDelegate <out T>();

public static void Test()
{
    SampleGenericDelegate <String> dString = () => " ";

    // You can assign delegates to each other, 
    // because the type T is declared covariant.
    SampleGenericDelegate <Object> dObject = dString;           
}

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

В следующем примере кода SampleGenericDelegate<String> нельзя явно преобразовать в SampleGenericDelegate<Object> (SampleGenericDelegate(Of String) в SampleGenericDelegate(Of Object) в Visual Basic), хотя 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
public delegate T SampleGenericDelegate<T>();

public static void Test()
{
    SampleGenericDelegate<String> dString = () => " ";

    // 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.
    SampleGenericDelegate<Object> dObject = () => " ";

    // The following statement generates a compiler error 
    // because the generic type T is not marked as covariant. 
    // SampleGenericDelegate <Object> dObject = dString;



}

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

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

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

Объявление вариативных параметров типа в универсальных методах-делегатах

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

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

Public Delegate Function DCovariant(Of Out R)() As R
public delegate R DCovariant<out R>();

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

Public Delegate Sub DContravariant(Of In A)(ByVal a As A)
public delegate void DContravariant<in A>(A a);

Важно!

Параметры ByRef в Visual Basic, а также параметры ref и out в C# нельзя пометить как вариативные.

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

Public Delegate Function DVariant(Of In A, Out R)(ByVal a As A) As R
public delegate R DVariant<in A, out R>(A a);

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

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

Dim dvariant As DVariant(Of String, String) = Function(str) str + " "
dvariant("test")
DVariant<String, String> dvariant = (String 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)
Action<object> actObj = x => Console.WriteLine("object: {0}", x);
Action<string> actStr = x => Console.WriteLine("string: {0}", x);
// All of the following statements throw exceptions at run time. 
// Action<string> actCombine = actStr + actObj; 
// actStr += actObj; 
// Delegate.Combine(actStr, actObj);

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

Вариативность для параметров универсального типа поддерживается только для ссылочных типов. Например, DVariant<int> (DVariant(Of Int) в Visual Basic) нельзя неявно преобразовать в DVariant<Object> или DVaraint<long> (DVariant(Of Object) или DVaraint(Of Long) в Visual Basic), поскольку integer является типом значения.

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

' 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 dVaraintInt 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 dVaraintObject As DInvariant(Of Object) = dInt 
    ' Dim dVaraintLong As DInvariant(Of Long) = dInt 
End Sub
// The type T is covariant. 
public delegate T DVariant<out T>();

// The type T is invariant. 
public delegate T DInvariant<T>();

public static void Test()
{
    int i = 0;
    DInvariant<int> dInt = () => i;
    DVariant<int> dVariantInt = () => 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. 
    // DInvariant<Object> dObject = dInt; 
    // DInvariant<long> dLong = dInt; 
    // DVariant<Object> dVariantObject = dVariantInt; 
    // DVariant<long> dVariantLong = dVariantInt;            
}

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

Ослабленное преобразование делегата, появившееся в Visual Basic 2008, обеспечивает большую гибкость при сопоставлении сигнатур методов с типами делегатов. Например, оно позволяет опустить спецификации параметров, а также возвращаемые значения функции при назначении метода делегату. Дополнительные сведения см. в разделе Неявное преобразование делегата (Visual Basic).

См. также

Задачи

Практическое руководство. Объединение делегатов (многоадресные делегаты) (Руководство по программированию в C#)

Ссылки

Использование вариативности в универсальных методах-делегатах Func и Action (C# и Visual Basic)

Другие ресурсы

Универсальные шаблоны в платформе .NET Framework