Search code examples
c#genericsimplicit-conversiontype-constraints

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


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

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

I'm trying to use some constraints on the type parameters to ensure that only valid types can be specified. However, when specifying a constraint defining that T must implement IAppointment<IAppointmentProperties>, the compiler gives an error when using a class that is Appointment<AppointmentProperties>:

class MyAppointment : Appointment<MyAppointmentProperties>
{
}

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

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

The error is:

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

Could anybody explain why this does not work?


Solution

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