5

Given the code below:

class Animal
{ }

class Dog : Animal
{ }

class Cage<T>
{
    private T animal;

    public Cage(T animal)
    {
        this.animal = animal;
    }

    public T Animal
    { 
        get { return animal;} 
    }
}

class Program
{
    static void Main(string[] args)
    {
        Dog dog = new Dog();
        Cage<Animal> animalCage = new Cage<Animal>(dog);
        Cage<Dog> dogCage = (Cage<Dog>)animalCage;
    }
}

How can I workaround the last compiler error (conversion from animalCage to dogCage)?

In my code I know that the cage contains a dog, but I'm not able to find a way to cast to it. Is my unique alternative to create a converter and create a new Cage<Dog> instance from the value of a Cage<Animal>?

Tahbaza
  • 9,486
  • 2
  • 26
  • 39
user1778378
  • 51
  • 1
  • 2
  • You may know that the cage contains a dog, but the *type system* doesn't. Java generics will let you off with a warning because generic arguments are erased during compilation, but CLR generics are maintained into runtime. – Jeffrey Hantin Oct 26 '12 at 23:22
  • You are right... my question was exactly if there is a special declaration (or c# keyword) to tell compiler "thrust me" :) – user1778378 Oct 26 '12 at 23:47
  • No such luck -- there is no way to turn off CLR generic type enforcement at runtime. – Jeffrey Hantin Oct 27 '12 at 00:05

3 Answers3

4

Problem #1: You cannot turn a Cage<Animal> instance into a Cage<Dog> instance, you need a Cage<Dog> instance (or an instance of a more specific type) whose reference is stored in a variable of a less specific type.

Change

Cage<Animal> animalCage = new Cage<Animal>(dog);
Cage<Dog> dogCage = (Cage<Dog>)animalCage;

to

Cage<Animal> animalCage = new Cage<Dog>(dog);
Cage<Dog> dogCage = (Cage<Dog>)animalCage;

Problem #2: You cannot store the reference to a Cage<Dog> instance in a Cage<Animal> variable, because classes do not support co-/contravariance.

Change

class Cage<T>
{
    ...

to

interface ICage<out T> 
{
    T Animal { get; }
}

class Cage<T> : ICage<T> 
{

and

Cage<Animal> animalCage = new Cage<Dog>(dog);
Cage<Dog> dogCage = (Cage<Dog>)animalCage;

to

ICage<Animal> animalCage = new Cage<Dog>(dog);
ICage<Dog> dogCage = (Cage<Dog>)animalCage;

Then it works. (If you do not change new Cage<Animal> to new Cage<Dog>, you get a cast exception at runtime.)

dtb
  • 213,145
  • 36
  • 401
  • 431
  • Yes, this works, but unfortunetly I'm not sure I can switch to interfaces. The Cage object has to pass through a WCF service, and interfaces resolves in generic "object" (with KnownTypes related issues...). Sorry, this was an attempt of simplification of a big problem :) – user1778378 Oct 26 '12 at 23:44
  • I'm not sure how we can help. You need to change something in your design, but without knowing more, it's difficult to suggest anything. – dtb Oct 26 '12 at 23:51
  • @user1778378 KnownTypes related issues? You can subclass [`DataContractResolver`](http://msdn.microsoft.com/en-us/library/system.runtime.serialization.datacontractresolver.aspx) to control the mapping of actual runtime types to and from XML. – Jeffrey Hantin Oct 27 '12 at 00:41
2

This would a job for generic variance, except that T should normally be covariant (since it's an output) and you are attempting to use it in contravariant fashion. Also, variance is not applicable to classes, only to interfaces and delegates, so you'd need to define an ICage<T>.

Covariance would allow the cast in the other direction: you could cast ICage<Dog> to ICage<Animal>. Contravariance would lead to a contradiction, since you would be able to attempt to cast a Cage<Animal> containing a Cat to a Cage<Dog>, rendering get_Animal ill-typed.

Arrays are covariant as well: you can cast a Dog[] to an Animal[], but you can't cast an Animal[] to a Dog[] even if you know it contains only dogs.

The next thing I thought of was defining an explicit conversion operator, but these can't be generic.

In the end, you'll need to construct a new Cage<Dog> to make this work.

Community
  • 1
  • 1
Jeffrey Hantin
  • 35,734
  • 7
  • 75
  • 94
  • (Co)variance is applied for interfaces, not classes. You'd might want to modify your answer to reflect that. – Adi Lester Oct 26 '12 at 23:05
  • 1
    I thought about it for a bit: irrespective of variance being limited to delegates and interfaces, what he's looking for is an unsafe narrowing cast, and allowing it would lead to a contradiction in the type system. – Jeffrey Hantin Oct 26 '12 at 23:06
0

You can add a generic constraint

class Cage<T> where T : Animal

and then just return the base class from your method

public Animal Animal
{ 
    get { return animal;} 
}

The generic constraint informs the compiler that there are limits on the types that can be supplied for T. If you supply something else, e.g. Cage<object>, you will get a compile time error.

Eric J.
  • 147,927
  • 63
  • 340
  • 553