Ingweland
Ingweland

Reputation: 1077

Casting generic MyClass<Child> to MyClass<Parent> in C#

Suppose I have a parent class Animal, with subclasses Cat and Dog. I also have a Building<T> where T can be of any Animal.

public class<T> Building
{
    public T Inhabitant {get; set;}
}

Now, I want to create some buildings with cats and some with dogs (based on parameters available only at runtime). For this, I am trying to create following method:

private Building<Animal> populateBuilding(string animalKind)
{
    if(animalKind == "cat")
    {
        return new Building<Cat>;
    }
    if(animalKind == "dog")
    {
        return new Building<Dog>;
    }
    return null;
}

However, I cannot do this because compiler tells that Building<Dog>/Building<Cat> cannot be cast to Building<Animal>. I found several examples where similar issue is described but with using of lists (e.g. List<Animal>, List<Dog>, List<Cat>). However, I do not use lists in my case, and wonder if it is still same situation.

Upvotes: 0

Views: 67

Answers (2)

ach
ach

Reputation: 2373

You should read some article on covariance and contravariance, for instance, this one.

In your case the class Building is so designed that it does not allow for either.

Suppose it were allowed to use Building covariantly. Then it would permit the following code snippet:

var dogHouse = new Building<Dog>();
var house = (Building<Animal>) dogHouse; // Casting to base class

house.Inhabitant = new Cat(); // Inhabitant of a Building<Animal> is any Animal
dogHouse.Inhabitant.Bark(); // Meow!

A generic may be used covariantly with its type parameter T if its methods only return values of type T (as return values or out-parameters). A generic may be used covariantly with its type parameter T if its methods only accept values of type T as parameters. In your case, there are both, namely, get_Inhabitant returns an Animal and set_Inhabitant accepts an Animal.

Moreover, in C#, as it has been rightly pointed out in comments, the concepts of covariance and contravariance are only applied to interfaces and delegates.

Upvotes: 3

lintmouse
lintmouse

Reputation: 5119

You should be able to do it as follows:

private Building<Animal> populateBuilding(string animalKind)
{
    var building = new Building<Animal>();

    if (animalKind == "cat")
    {
        return new Building<Animal>
        {
            Inhabitant = new Cat()
        };
    }
    if (animalKind == "dog")
    {
        return new Building<Animal>
        {
            Inhabitant = new Dog()
        };
    }

    return null;
}

Upvotes: 0

Related Questions