Dlaczego ograniczenie typu ogólnego powoduje brak implicit reference conversion error?

Stworzyłem kilka interfejsów i klas generycznych do pracy z terminami:

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

Próbuję użyć pewnych ograniczeń na parametrach typu, aby upewnić się, że tylko prawidłowe typy mogą być określone. Jednak przy określaniu ograniczenia definiującego, że T musi zaimplementować IAppointment<IAppointmentProperties>, kompilator wyświetla błąd podczas używania klasy Appointment<AppointmentProperties>:

class MyAppointment : Appointment<MyAppointmentProperties>
{
}

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

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

Błąd to:

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>'.

Czy ktoś mógłby wyjaśnić, dlaczego to nie działa?
Author: Rens, 2013-07-03

4 answers

Uprośćmy:

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>();

Twoje pytanie brzmi: dlaczego ostatnia linijka jest nielegalna?

Teraz, gdy przepisałem kod, aby go uprościć, powinno być jasne. An ICage<IAnimal> jest klatką, w której można umieścić każde zwierzę , Ale Cage<Tiger> może trzymać tylko tygrysy , więc to musi być nielegalne.

Gdyby to nie było nielegalne to mógłbyś to zrobić:

cage.Enclose(new Fish());
I właśnie wsadziłeś rybę do klatki tygrysa.

System typów nie pozwala na to konwersja, ponieważ naruszałoby to zasadę, że możliwości typu źródłowego nie mogą być mniejsze niż możliwości typu docelowego. (Jest to forma słynnej "Zasady substytucji Liskowa".)

Dokładniej, powiedziałbym, że nadużywasz leków generycznych. Fakt, że stworzyłeś relacje typu, które są zbyt skomplikowane, aby analizować siebie, jest dowodem na to, że powinieneś uprościć całą sprawę; jeśli nie zachowujesz wszystkich typów relacje proste i napisałeś coś wtedy twoi użytkownicy na pewno nie będą w stanie utrzymać tego prosto.
 97
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-03 20:16:53

Jest już bardzo dobra odpowiedź Erica. Chciałem tylko wykorzystać tę szansę, by porozmawiać o niezmienności , Kowariancja i Kontrawarancja tutaj.

definicje można znaleźć w https://msdn.microsoft.com/en-us/library/dd799517 (v=vs.110). aspx


Powiedzmy, że jest tam zoo.
abstract class Animal{}
abstract class Bird : Animal{}
abstract class Fish : Animal{}
class Dove : Bird{}
class Shark : Fish{}

Zoo się przenosi, więc jego zwierzęta muszą zostać przeniesione ze starego zoo do nowego jeden.

Niezmienniczość

Zanim je przeniesiemy, musimy umieścić zwierzęta w różnych pojemnikach. Wszystkie pojemniki wykonują te same operacje: włożyć do nich zwierzę lub wyciągnąć z niego zwierzę.
interface IContainer<T> where T : Animal
{
    void Put(T t);
    T Get(int id);
}

Oczywiście dla ryb potrzebujemy zbiornika:

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

Aby rybę można było wsadzić i wydostać się ze zbiornika (miejmy nadzieję, że nadal żyje):

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

Załóżmy, że możemy zmienić go na IContainer<Animal>, wtedy możesz przypadkowo umieścić gołębia w zbiorniku, która stanie się niepomyślną tragedią.

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

Kontrawariancja

W celu poprawy skuteczności, zespół zarządzający zoo decyduje się na oddzielenie procesu załadunku i rozładunku (kierownictwo zawsze to robi). Więc mamy dwie oddzielne operacje, jedna tylko do załadunku, druga rozładunku.

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

Wtedy mamy klatkę dla ptaków:

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

Kowariancja

W nowym zoo mamy zespół do rozładunku zwierząt.
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();

Z zespołu z punktu widzenia, nie ma znaczenia, co jest w środku, po prostu rozładowują zwierzęta z pojemników.

 8
Author: Stephen Zeng,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-15 11:10:59

Ponieważ zadeklarowałeś swoją klasę MyAppointment używając konkretnego typu, a nie interfejsu. Należy zadeklarować w następujący sposób:

class MyAppointment : Appointment<IAppointmentProperties> {
}

Teraz konwersja może nastąpić bezwarunkowo.

Deklarując AppointmentEntry<T> z ograniczeniem where T: IAppointment<IAppointmentProperties> tworzysz kontrakt, w którym nieokreślony typ dla AppointmentEntry<T> musi pomieścić dowolny typ zadeklarowany przez IAppointmentProperties. Deklarując typ z konkretną klasą naruszyłeś ten kontrakt (implementuje a typ IAppointmentProperties, ale nie żadnego typu).

 6
Author: Peter Gluck,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-07-02 20:23:13

Zadziała, jeśli ponownie zdefiniujesz przykładowy interfejs z:

interface ICage<T>

Do

interface ICage<out T>

(zwróć uwagę na słowo kluczowe out)

Wtedy następujące stwierdzenie jest poprawne:

ICage<IAnimal> cage = new Cage<Tiger>();
 0
Author: Kaginawa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-15 09:12:24