1

I have two interfaces:

public interface ISnack { }

public interface IWrapper<TSnack> where TSnack : ISnack
{
   void Wrap(TSnack snack);
}

And a two classes:

public class CheeseCakeContainer : IWrapper<CheeseCake>
{
    CheeseCake c;
    public void Wrap(CheeseCake snack)
    {
        c = snack;
    }
}

public class CheeseCake : ISnack { }

I want to execute

IWrapper<ISnack> wrappedSnack = (IWrapper<ISnack>)(new CheeseCakeContainer());
var c1 = new CheeseCake();
wrappedSnack.Wrap(c1);

But this throws an invalid cast exception

System.InvalidCastException: 'Unable to cast object of type 'CheeseCakeContainer' to type 'IWrapper`1[ISnack]'.'

What should I change to make the cast work? I am using C#9 and .NET5

Camilo Terevinto
  • 31,141
  • 6
  • 88
  • 120
  • 2
    This does not involve covariance or contravariance. Those are indicated by generic parameters with the `in` or `out` keywords – Andrew Williamson Dec 17 '20 at 22:09
  • 2
    Agree with @AndrewWilliamson - there is something wrong with either text or samples in the question. Please review the question and [edit] to either use correct terms or show correct samples. – Alexei Levenkov Dec 17 '20 at 22:12
  • In current state it looks like regular "cast Generic to Generic" (https://stackoverflow.com/questions/41179199/cast-genericderived-to-genericbase) question. – Alexei Levenkov Dec 17 '20 at 22:15
  • @AndrewWilliamson To my understanding this is an example of implicit covariance, see https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/. Casting CheeseCakeContainer to IWrapper should be considered covariant because IEnumerable and IEnumerable is covariant. –  Dec 17 '20 at 22:19
  • 1
    That's because IEnumerable is explicitly declared as [IEnumerable](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1?view=net-5.0). Your question does not currently involve [variance](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/creating-variant-generic-interfaces) at all, and I'm not sure that it would help. Perhaps you could give us more detail on how you intend to use these interfaces, so we can get a better understanding of your requirements – Andrew Williamson Dec 17 '20 at 22:23
  • @AndrewWilliamson Let's assume that it is not related to co/contra- variance. What do I need to change to make the cast work? –  Dec 17 '20 at 22:32
  • What types do you know at compile time, and what types do you have to work out at runtime? It would really help if you gave some more details about what you're trying to achieve with this, and maybe a more detailed example – Andrew Williamson Dec 17 '20 at 22:35
  • All known types are given in the example and known at compile-time. This shows everything that I am trying to achieve. If you paste it into a console app you will have the same code that I do. –  Dec 17 '20 at 22:40
  • If all the types are known at compile time, why do you have to cast anything? – Andrew Williamson Dec 17 '20 at 23:02

1 Answers1

0

The crux of your issue is that you are trying to create a container that is covariant, or where you are assigning a more derived type to a more generic type (CheeseCake to ISnack) while simultaneously attempting to call an interface method that should be contravariant, or implementing a more derived type (CheeseCake). This type of bi-directional variance is illegal in C#.

As a result, you have essentially two options. You can abandon the covariance during your container creation which allows you to maintain contravariance in your implementation, or you can use covariance during your container creation, but you will be forced to cast to your target type in your implementation in order to satisfy the interface.

The former with a contravariant IWrapper<in TSnack> definition:

CheeseCakeContainer wrappedSnack = new CheeseCakeContainer();
var c1 = new CheeseCake();
wrappedSnack.Wrap(c1);


public interface ISnack { }
public interface IWrapper<in TSnack> where TSnack : ISnack
{
    void Wrap(TSnack snack);
}

public class CheeseCakeContainer : IWrapper<CheeseCake>
{
    public void Wrap(CheeseCake snack)
    {
        snack.Type = "Strawberry";
    }
}

public class CheeseCake : ISnack 
{ 
    public string Type { get; set; }    
}

The latter:

IWrapper<ISnack> wrappedSnack = new CheeseCakeContainer();
var c1 = new CheeseCake();
wrappedSnack.Wrap(c1);

public interface ISnack { }
public interface IWrapper<out TSnack> where TSnack : ISnack
{
    void Wrap(ISnack snack);
}

public class CheeseCakeContainer : IWrapper<CheeseCake>
{
    public void Wrap(ISnack snack)
    {
        ((CheeseCake)snack).Type = "Strawberry";
    }
}

public class CheeseCake : ISnack
{
    public string Type { get; set; }
}
David L
  • 32,885
  • 8
  • 62
  • 93