1

In some situations I need to cast down an object to an interface to fit my needs, which implicitly requires to cast down type arguments of generic interfaces.

Example

ICage<TAnimal> is the interface for a Cage of an animal of type IAnimal

public interface ICage<TAnimal>
    where TAnimal : IAnimal<IOwner>

public class Cage<TAnimal> : ICage<TAnimal>
    where TAnimal : IAnimal<IOwner>

public interface IAnimal<out TOwner>
    where TOwner : IOwner

IAnimal needs an Owner of type IOwner

public abstract class Mammal<TOwner> : IAnimal<TOwner>
    where TOwner : IOwner

A Mammal is a type of Animal with an Owner of type IOwner.

public class Human : IOwner

A Human is a type of IOwner

public class Dog<TOwner> : Mammal<TOwner>
    where TOwner : IOwner

A Dog is a type of Mammal.

Now putting everything together:

 var cage = new Cage<Mammal<IOwner>>();
 var me = new Human()
 {
     Name = "Hakim"
 };
 var dog = new Dog<Human>();
 dog.Owner = me;
 cage.Add((Mammal<IOwner>)dog);

In the last line I get a compile time error CS0030 telling me that I can not convert Dog to Mammel.

Hakim
  • 452
  • 4
  • 15
  • Interestingly, a version of this question came up earlier today: https://stackoverflow.com/questions/53960364/casting-type-to-interface-with-a-generic-type-constraint. That was found to be a duplicate of https://stackoverflow.com/questions/15596005/c-sharp-generics-interface-covariance. I particularly liked the comment on today's question (by @vc74): _If the second worked on any interface, you'd be able to call `List.Add(fish)` with both `Panther` and `Fish` implementing `IAnimal`_. Your case is harder, I guess you could have a `Neanderthal` implementation of `IOwner` – Flydog57 Dec 29 '18 at 00:51
  • It should work if you use specific types in your `cage` definition: `var cage = new Cage>();` and then just pass `dog` to the `Add` method without any casting. Alternatively, you could make both arguments generic, like: `var cage = new Cage>();` and it should also work. – Rufus L Dec 29 '18 at 00:53
  • @Rufus L: Is using an explicit conversion operator an option – Hakim Dec 29 '18 at 16:31
  • You don't need to cast at all. – Rufus L Dec 29 '18 at 17:15

1 Answers1

3

This is the same reason why you can't cast a List<string> to a List<object>.

Let's say in Mammal there is a property called Owner like this:

public TOwner Owner { get; set; }

For an instance of Mammal<IOwner>, this becomes:

public IOwner Owner { get; set; }

dog is a Dog<Human>, which is also a Mammal<Human>. If you could cast dog to Mammal<IOwner>, this would mean that dog.Owner can suddenly store any type that implements IOwner. i.e. this would be possible:

class EvilOwner : IOwner { ... }

Mammal<IOwner> mammal = (Mammal<IOwner>)dog;
mammal.Owner = new EvilOwner();

But that's not possible because dog at runtime is a Dog<Human>, which has an Owner of Human. EvilOwner cannot possibly be stored in Human!

What I suggest you do is to remove the TOwner generic parameter. If in your mind that Dog<Human> is also a kind of Mammal<IOwner>, then it probably makes more sense to design the classes like this:

public interface IAnimal {
    IOwner Owner { get; }
}

public abstract class Mammal : IAnimal { ... }

public class Dog : Mammal { ... }
Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • With your suggestion I gain some flexibility but lose the ability to say by example that a Dog has to have a Human as it's Owner. (You could set a SetOwner(IOwner) method on IAnimal and perform some sort of type check / conversion but that's not ideal). – Hakim Dec 30 '18 at 10:09
  • @Hakim then you _can’t_ convert `Dog` to `Mammal`. If that were allowed, you could set the dog’s owner to something other than a `Human`, couldn’t you? – Sweeper Dec 30 '18 at 10:14
  • You are absolutely right there, but in case of a cast from [List to List](https://stackoverflow.com/a/1817332/10195843) casting the items in it is possible?! – Hakim Dec 30 '18 at 10:32
  • @Hakim I see. Then you must create a _new_ instance of `Dog` using your instance of `Dog`, just like in order convert a `List` to a `List`, you need to create a _new_ list. And after that, cast to `Mammal`. – Sweeper Dec 30 '18 at 10:34