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


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

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

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

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> типов можно назначить один из следующих методов этим делегатам.

// Matching signature.  
public static First ASecondRFirst(Second second)  
{ 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.  
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#)" и "Использование вариативности для Func и Action Generic Delegates (C#)".

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

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

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

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

// 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>, хотя String наследует Object. Эту проблему можно устранить, пометив универсальный параметр T ключевым словом out .

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

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

Дополнительные сведения и примеры см. в разделе "Использование вариативности" для func и action generic делегатов (C#).

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

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

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

public delegate R DCovariant<out R>();  

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

public delegate void DContravariant<in A>(A a);  

Это важно

ref, inи out параметры в C# не могут быть помечены как вариант.

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

public delegate R DVariant<in A, out R>(A a);  

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

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

DVariant<String, String> dvariant = (String str) => str + " ";  
dvariant("test");  

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

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

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<Object> или DVariant<long>, так как целое число является типом значения.

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

// 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;
}  

См. также