0

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 :)

Sander
  • 3
  • 1
  • Does this answer your question? [Understanding Covariant and Contravariant interfaces in C#](https://stackoverflow.com/questions/2719954/understanding-covariant-and-contravariant-interfaces-in-c-sharp) – Charlieface Mar 10 '21 at 17:16

1 Answers1

0

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.

Johnathan Barclay
  • 18,599
  • 1
  • 22
  • 35
  • Thank you for your answer, I will look into this subject some more because it ain't landing completely yet. – Sander Mar 10 '21 at 16:55
  • Yes, it's not immediately obvious. One simple example of why variance matters: `List fruits = new List(); fruits.Add(new Banana());`. Clearly this shouldn't be allowed, and is why the compiler enforces it. – Johnathan Barclay Mar 10 '21 at 17:04
  • Yeah I do understand that. Am now looking further into the `in` `out` keywords and how this triggers different variances :) Thanks – Sander Mar 10 '21 at 17:11
  • When I specify `T` to be covariant in `IBag` (using `out`), then make `T` contravariant in `IGroceryStorage` and also make `T` contravariant in `IStorage` (using `in`) I get the following error: `Invalid variance: The type parameter 'T' must be contravariantly valid on 'IGroceryStorage'. 'T' is covariant.` – Sander Mar 10 '21 at 18:38
  • Apologies, that should have been _covariant_. Each interface needs the `out` modifier. – Johnathan Barclay Mar 10 '21 at 19:33
  • If I would now create an abstract base class BaseBag implementing IBag which has a generic IEnumerable property that could be replaced by a more derived type like IEnumerable in case of ICoolBag. How would one do so? @johnathan – Sander Mar 11 '21 at 15:03