2

I have the following block of code as a simplified example of a problem I'm having. but I get an error claiming that I cannot convert one type to another. I used LINQPad to test it.

void Main()
{
    LivingThing<Appendage> mysteryAnimal = new Cat();
}

public class Appendage { }
public class Paw : Appendage { }

public class LivingThing<TExtremity> where TExtremity : Appendage { }
public class Animal<TExtremity> : LivingThing<TExtremity> where TExtremity : Appendage { }
public class Cat : Animal<Paw> { }

Why can't I cast Cat to a LivingThing<Appendage> when I know that Cat's definition is using sub classes of LivingThing and Appendage?

Falanwe
  • 4,636
  • 22
  • 37
Nick Albrecht
  • 16,607
  • 10
  • 66
  • 101
  • 1
    Because `LivingThing` is not `LivingThing` - read on covariance... – Alexei Levenkov Mar 11 '15 at 16:40
  • 3
    Classes are invariant - you could create an `ILivingThing` interface and then you could do `ILivingThing mysteryAnimal = new Cat();`. – Lee Mar 11 '15 at 16:40
  • Oh, so classes are covariant, but not generics? `LivingThing mysteryAnimal = new Animal(); //works` `LivingThing mysteryAnimal = new LivingThing(); //does not` – Nick Albrecht Mar 11 '15 at 16:50
  • The alleged duplicate is clearly related, but it does not answer OP's question on why it is impossible to do what he wants without changing things around. Voting to reopen. – Sergey Kalinichenko Mar 11 '15 at 16:54

2 Answers2

4

It is easier to see why what you are trying to do cannot work without some modifications by adding a method to one of your classes:

public class LivingThing<TExtremity> where TExtremity : Appendage {
    private TExtremity extremity;
    public void SetExtremity(TExtremity e) {
        extremity = e;
    }
}

Now let's imagine that C# lets you do your assignment. Then it should let you do this:

public class Hand : Appendage { }
...
// Let's pretend this works
LivingThing<Appendage> cat = new Cat();
// Now that C# let us do the assignment above, it must allow this too,
// because Cat is a LivingThing and Hand is an Appendage:
cat.SetExtremity(new Hand());

Oops, we've got a Cat with a Hand! C# should not let us do this.

Doing what you wish to do may be possible if LivingThing had methods that return TExtremity, though. C# provides means for defining inheritance hierarchies that give you flexibility of assignments along the lines of what you tried. Here is your modified code that works:

void Main()
{
    ILivingThing<Appendage> mysteryAnimal = new Cat();
}

public class Appendage { }
public class Paw : Appendage { }

public interface ILivingThing<out TExtremity> where TExtremity : Appendage { }
// You have a choice of keeping Animal a class. If you do, the assignment
// Animal<Appendage> mysteryAnimal = new Cat()
// would be prohibited.
public interface IAnimal<TExtremity> : ILivingThing<out TExtremity> where TExtremity : Appendage { }
public class Cat : Animal<Paw> { }

There is a catch: neither ILivingThing<TExtremity> nor IAnimal<TExtremity> is allowed to have settable properties of type TExtremity or methods that take TExtremity as an argument.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • Very interesting... I popped your example into LINQPad to try it out and it hiccuped on a few issues. The `ILivingThing` on the IAnimal interface apparently needs to not be `out`? And `Cat` needs to inherit from the interface (not the class I had before). – Nick Albrecht Mar 11 '15 at 17:50
  • I'm familiar with `out` on parameters of methods, but not using it with generics. Is there a good question SO or an article that covers them? – Nick Albrecht Mar 11 '15 at 17:50
  • 1
    @NickAlbrecht [Eric Lippert](http://stackoverflow.com/users/88656/eric-lippert), one of the designers of C# compiler, has a very nice series of posts about covariance. In [this post](http://blogs.msdn.com/b/ericlippert/archive/2007/10/31/covariance-and-contravariance-in-c-part-eight-syntax-options.aspx) he discusses syntax options for using `in` and `out` in template arguments before it got added to the language (see option #5). – Sergey Kalinichenko Mar 11 '15 at 17:58
  • Fantastic, thanks a lot for the point in the right direction. I appreciate you taking the time to help me out :-) – Nick Albrecht Mar 11 '15 at 18:10
1

What you're trying to do is called "covariance"; assigning an instance of a generic class with a more derived generic parameter to a variable of a class with a less derived one.

In C#, this is not supported for classes. For interfaces, it must be explicitly specified. The following would compile:

void Main()
    {
        ILivingThing<Appendage> mysteryAnimal = new Cat();
    }

    public class Appendage { }
    public class Paw : Appendage { }

    public interface ILivingThing<out TExtremity> where TExtremity : Appendage { }
    public class Animal<TExtremity> : ILivingThing<TExtremity> where TExtremity : Appendage { }
    public class Cat : Animal<Paw> { }

Whether this will be acceptable for your actual code depends on how LivingThing is defined; if it is a "marker class" (no members; existing solely to be a point of derivation identifying its children as LivingThings), or an abstract class with no non-abstract members, it should work like a charm. If you have member code in this class, you'll need to extract a covariant interface from the class.

KeithS
  • 70,210
  • 21
  • 112
  • 164
  • What if I did both, have member code in the base class (non abstract) but use an interface for using as the restriction on my generic where constraints? – Nick Albrecht Mar 11 '15 at 17:52
  • Ok. Awesome I'll have to look into how I'm passing these instances around and determine when I actually need a strongly typed class or just an interface, and see if I can get it all sorted out. Thanks so much :-) – Nick Albrecht Mar 11 '15 at 18:09