multiple - c# generic parameter where



Почему ограничение общего типа приводит к неявной ошибке преобразования ссылок? (3)

Давайте упростим:

interface IAnimal { ... }
interface ICage<T> where T : IAnimal { void Enclose(T animal); } 
class Tiger : IAnimal { ... }
class Fish : IAnimal { ... }
class Cage<T>  : ICage<T> where T : IAnimal { ... }
ICage<IAnimal> cage = new Cage<Tiger>();

Ваш вопрос: почему последняя строка является незаконной?

Теперь, когда я переписал код, чтобы упростить его, это должно быть ясно. ICage<IAnimal> - это клетка, в которую вы можете разместить любое животное , но Cage<Tiger> может содержать только тигры , поэтому это должно быть незаконным.

Если бы это не было незаконным, вы могли бы сделать это:

cage.Enclose(new Fish());

И эй, ты просто положил рыбу в клетку тигра.

Система типов не допускает этого преобразования, потому что это приведет к нарушению правила, что возможности типа источника не должны быть меньше возможностей целевого типа. (Это форма знаменитого «принципа замены Лискова»).

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

Я создал пару интерфейсов и общие классы для работы с назначением на повестку дня:

interface IAppointment<T> where T : IAppointmentProperties
{
    T Properties { get; set; }
}

interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties>
{
    DateTime Date { get; set; }
    T Appointment { get; set; }
}

interface IAppointmentProperties 
{
    string Description { get; set; }
}

class Appointment<T> : IAppointment<T> where T : IAppointmentProperties
{
    public T Properties { get; set; }
}

class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties>
{
    public DateTime Date { get; set; }
    public T Appointment { get; set; }
}

class AppointmentProperties : IAppointmentProperties
{
    public string Description { get; set; }
}

Я пытаюсь использовать некоторые ограничения для параметров типа, чтобы гарантировать, что могут быть указаны только допустимые типы. Однако при указании ограничения, определяющего, что T должен реализовывать IAppointment<IAppointmentProperties> , компилятор дает ошибку при использовании класса, который является Appointment<AppointmentProperties> :

class MyAppointment : Appointment<MyAppointmentProperties>
{
}

// This goes wrong:
class MyAppointmentEntry : AppointmentEntry<MyAppointment>
{
}

class MyAppointmentProperties : AppointmentProperties
{
    public string ExtraInformation { get; set; }
}

Ошибка:

The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.

Может ли кто-нибудь объяснить, почему это не работает?


Answer #1

Он будет работать, если вы переопределите интерфейс образца:

interface ICage<T>

в

interface ICage<out T>

(обратите внимание на ключевое слово out )

то верно следующее утверждение:

ICage<IAnimal> cage = new Cage<Tiger>();

Answer #2

Эрик очень хорошо ответил. Просто хотел воспользоваться этой возможностью, чтобы поговорить об Инвариантности , Ковариации и Контравариантности здесь.

Для определений см. https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx

Предположим, есть зоопарк.

abstract class Animal{}
abstract class Bird : Animal{}
abstract class Fish : Animal{}
class Dove : Bird{}
class Shark : Fish{}

Зоопарк перемещается, поэтому его животных нужно переместить из старого зоопарка в новый.

неизменность

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

interface IContainer<T> where T : Animal
{
    void Put(T t);
    T Get(int id);
}

Очевидно, для рыбы нам нужен бак:

class FishTank<T> : IContainer<T> where T : Fish
{
    public void Put(T t){}
    public T Get(int id){return default(T);}
}

Так что рыбу можно положить и вытащить из бака (надеюсь, все еще жив):

IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same
fishTank.Put(new Shark());          
var fish = fishTank.Get(8);

Предположим, что нам разрешено изменить его на IContainer<Animal> , тогда вы можете случайно поставить голубь в танк, что может произойти беззазорной трагедией.

IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed
fishTank.Put(new Shark());
fishTank.Put(new Dove()); //Dove will be killed

контрвариация

Чтобы повысить эффективность, команда управления зоопарком ограничивает процесс загрузки и выгрузки (управление всегда делает это). Таким образом, у нас есть две отдельные операции: одна для загрузки, другая - разгрузка.

interface ILoad<in T> where T : Animal
{
    void Put(T t);
}

Тогда у нас есть клетка для птиц:

class BirdCage<T> : ILoad<T> where T : Bird
{
    public void Put(T t)
    {
    }
}

ILoad<Bird> normalCage = new BirdCage<Bird>();
normalCage.Put(new Dove()); //accepts any type of birds

ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove
doveCage.Put(new Dove()); //only accepts doves

ковариации

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

interface IUnload<out T> where T : Animal
{
    IEnumerable<T> GetAll();
}

class UnloadTeam<T> : IUnload<T> where T : Animal
{
    public IEnumerable<T> GetAll()
    {
        return Enumerable.Empty<T>();
    }
}

IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal
var animals = unloadTeam.GetAll();

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





type-constraints