Изучение различий среди делегатов

Завершено

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

Что такое дисперсию?

Вариативность — это концепция в C#, которая позволяет использовать более производный тип, чем тот, который указан в делегате или интерфейсе. Это позволяет создавать более гибкий и многократно используемый код, позволяя назначать методы делегатам, даже если их подписи не совпадают точно.

Существует два типа дисперсии:

  • Ковариация: позволяет методу иметь тип возвращаемого значения, который является более производным, чем тип, определенный в делегате.
  • Контравариантность: позволяет методу принимать параметры, которые менее производными, чем параметры в типе делегата.

Поддержка вариативности включает универсальные и негибкие делегаты.

Вариативность с пользовательскими делегатами

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

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


public class Animal { }
public class Dog : Animal { }

// Define a delegate that takes a Dog and returns an Animal
public delegate Animal AnimalDelegate(Dog dog);

// Method that matches the delegate signature
public static Animal GetAnimal(Dog dog) => new Animal();

// Method that uses covariance (returns a more derived type)
public static Dog GetDog(Dog dog) => new Dog();

// Method that uses contravariance (accepts a less derived type)
public static Animal GetAnimalFromAnimal(Animal animal) => new Animal();

public class Program
{
    public static void Main()
    {
        AnimalDelegate del;

        // Assign method with matching signature
        del = GetAnimal;
        Animal animal = del(new Dog());

        // Assign method with covariant return type
        del = GetDog;
        animal = del(new Dog());

        // Assign method with contravariant parameter type
        del = GetAnimalFromAnimal;
        animal = del(new Dog());
    }
}

В этом примере GetDog может быть назначен AnimalDelegate, потому что Dog является более производным типом, чем Animal (ковариантность). Аналогичным образом, GetAnimalFromAnimal можно присвоить, так как Animal является менее производным классом, чем Dog (контравариантность).

Вариация со строго типизированными делегатами

Вариативность также применяется к строго типизированным делегатам, таким как Action и Func.

Ковариация с Func

В следующем примере демонстрируется ковариация с Func:


public class Person { }
public class Employee : Person { }

public static Employee FindEmployee(string title) => new Employee();

public class Program
{
    public static void Main()
    {
        // Func<string, Person> can hold a method that returns Employee
        Func<string, Person> func = FindEmployee;
        Person person = func("Manager");
    }
}

В этом примере FindEmployee возвращает Employee, которое является более производным, чем Person, поэтому его можно присвоить Func<string, Person>.

Контравариантность с Action

В следующем примере показана контравариантность с Action:


public class Animal { }
public class Dog : Animal { }

public static void HandleAnimal(Animal animal) { }

public class Program
{
    public static void Main()
    {
        // Action<Dog> can hold a method that takes Animal
        Action<Dog> action = HandleAnimal;
        action(new Dog());
    }
}

HandleAnimal принимает Animal, который является менее производным, чем Dog, поэтому его можно назначить Action<Dog>.

Ковариантность в обобщениях

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

Ковариантный универсальный тип

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

В следующем примере демонстрируется ковариация в универсальных шаблонах:


public interface ICovariant<out T> { T Get(); }

public class Animal { }
public class Dog : Animal { }

public class CovariantExample : ICovariant<Dog>
{
    public Dog Get() => new Dog();
}

public class Program
{
    public static void Main()
    {
        ICovariant<Animal> covariant = new CovariantExample();
        Animal animal = covariant.Get();
    }
}


В этом примере ICovariant<out T> позволяет назначить ICovariant<Dog> на ICovariant<Animal>, потому что Dog является более специализированным, чем Animal.

Универсальный контравариантный тип

Ключевое in слово используется для объявления контравариантного параметра универсального типа. Эта функция позволяет использовать менее производный тип в качестве типа параметра.

В следующем примере демонстрируется контравариантность в универсальных шаблонах:


public interface IContravariant<in T> { void Set(T value); }

public class Animal { }
public class Dog : Animal { }

public class ContravariantExample : IContravariant<Animal>
{
    public void Set(Animal value) { }
}

public class Program
{
    public static void Main()
    {
        IContravariant<Dog> contravariant = new ContravariantExample();
        contravariant.Set(new Dog());
    }
}

Здесь IContravariant<in T> позволяет назначить IContravariant<Animal> к IContravariant<Dog>, потому что Animal менее производный, чем Dog.

Ключевые моменты

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