Why does a generic type constraint result in a no implicit reference conversion error?

前端 未结 5 1458
滥情空心
滥情空心 2020-12-04 10:58

I have created a couple of interfaces and generic classes for working with agenda appointments:

interface IAppointment where T : IAppointmentPropert         


        
相关标签:
5条回答
  • 2020-12-04 11:19

    Because you declared your MyAppointment class using the concrete type rather than the interface. You should declare as follows:

    class MyAppointment : Appointment<IAppointmentProperties> {
    }
    

    Now the conversion can occur implicitly.

    By declaring AppointmentEntry<T> with the constraint where T: IAppointment<IAppointmentProperties> you are creating a contract whereby the unspecified type for AppointmentEntry<T> must accommodate any type that is declared with IAppointmentProperties. By declaring the type with the concrete class you have violated that contract (it implements a type of IAppointmentProperties but not any type).

    0 讨论(0)
  • 2020-12-04 11:22

    There is already a very good answer from Eric. Just wanted to take this chance to talk about the Invariance, Covariance, and Contravariance here.

    For definitions please see https://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance


    Let's say there is a zoo.

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

    The zoo is relocating, so its animals need to be moved from the old zoo to the new one.

    Invariance

    Before we move them, we need to put the animals into different containers. The containers all do the same operations: put an animal in it or get an animal out from it.

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

    Obviously, for fish we need a tank:

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

    So the fish can be put in and get out from the tank(hopefully still alive):

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

    Suppose we are allowed to change it to IContainer<Animal>, then you can accidentally put a dove in the tank, in which case tragedy will occur.

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

    Contravariance

    To improve efficiency, the zoo management team decides to separate the load and unload process (management always does this). So we have two separate operations, one for load only, the other unload.

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

    Then we have a birdcage:

    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
    

    Covariance

    In the new zoo, we have a team for unloading animals.

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

    From the team's point of view, it does not matter what it is inside, they just unload the animals from the containers.

    0 讨论(0)
  • 2020-12-04 11:22

    It will work if you re-define the sample interface from:

    interface ICage<T>
    

    to

    interface ICage<out T>
    

    (please notice the out keyword)

    then the following statement is correct:

    ICage<IAnimal> cage = new Cage<Tiger>();
    
    0 讨论(0)
  • 2020-12-04 11:35

    In case someone else also has this error message: I found the same interface defined twice in different namespaces and the classes that have been tryed to be linked together did not use the same interface.

    0 讨论(0)
  • 2020-12-04 11:40

    Let's simplify:

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

    Your question is: why is the last line illegal?

    Now that I have rewritten the code to simplify it, it should be clear. An ICage<IAnimal> is a cage into which you can place any animal, but a Cage<Tiger> can only hold tigers, so this must be illegal.

    If it were not illegal then you could do this:

    cage.Enclose(new Fish());
    

    And hey, you just put a fish into a tiger cage.

    The type system does not permit that conversion because doing so would violate the rule that the capabilities of the source type must not be less than the capabilities of the target type. (This is a form of the famous "Liskov substitution principle".)

    More specifically, I would say that you are abusing generics. The fact that you've made type relationships that are too complicated for you to analyze yourself is evidence that you ought to simplify the whole thing; if you're not keeping all the type relationships straight and you wrote the thing then your users surely will not be able to keep it straight either.

    0 讨论(0)
提交回复
热议问题