Search code examples
c#interfaceimplicit-conversionderived-class

No implicit reference conversion between derived interfaces


The goal

Constructing a concrete object implementing ICoolbag and only being able to store ICoolBagGrocery instead of any type of grocery with IGrocery.

The problem

The implementation below results in the following error:

The type 'SolidDistribution.Core.Bag.CoolBag.ICoolBag' cannot be used as type parameter 'T' 
in the generic type or method 'IBagStorage<T>'. There is no implicit reference conversion from
 'SolidDistribution.Core.Bag.CoolBag.ICoolBag' to 'SolidDistribution.Core.Bag.IBag<SolidDistribution.Core.IGrocery>'.

Implementation

Bag

// A bag that can only hold coolbag groceries.
public interface ICoolBag : IBag<ICoolBagGrocery> { }

// a bag that can hold any type of grocery.
public interface IBag<T> : IGroceryStorage<T> where T : IGrocery { }

Storage

// A storage that can store any type of grocery.
public interface IGroceryStorage<T> : IStorage<T> where T : IGrocery { }

// A storage that can store any type of bag.
public interface IBagStorage<T> : IStorage<T> where T : IBag<IGrocery> { }

// Base storage interface.
public interface IStorage<T> { }

Grocery

// A grocery that can only be stored in a coolbag.
public interface ICoolBagGrocery : IGrocery { }

// Base grocery interface.
public interface IGrocery { }

Box

// A box with a bag that can only hold coolbag groceries.
public interface ICoolBox : IBoxWithBag<ICoolBag> { }

// Base box with bag storage interface.
public interface IBoxWithBag<T> : IBox, IBagStorage<T> where T : IBag<IGrocery> { }

// Base box interface.
public interface IBox { }

Note

Changing ICoolbag to use IGrocery instead of ICoolBagGrocery like so: (public interface ICoolBag : IBag<IGrocery> { }) fixes the error, but at the same time enables the ability to put any type of grocery in a coolbag. Which is obviously not supposed to happen :)


Solution

  • Your compilation error is because T is invariant in IBag<T>.

    ICoolBag is-a IBag<ICoolBagGrocery>, but IBag<ICoolBagGrocery> is not a IBag<IGrocery>.

    If you were to make T covariant in IBag<T> (using out), then IBag<ICoolBagGrocery> would be a IBag<IGrocery>:

    public interface IBag<out T> : IGroceryStorage<T> where T : IGrocery { }
    

    However, this would place restrictions on your IBag<T> interface: properties of type T would not allow set, and methods could only use T as the return type, not an argument type.

    For example:

    public interface IBag<out T> : IGroceryStorage<T> where T : IGrocery
    {
        T SomeProperty { get; } // Allowed
        T AnotherProperty { get; set; } // Compilation error
    
        T SomeMethod(); // Allowed
        void AnotherMethod(T t); // Compilation error
    }
    

    Furthermore, the variance would rise through the inheritance hierarchy, meaning T would also need to be covariant in IGroceryStorage<T> and IStrorage<T> to make this valid.