Covariance in Generics: Creating a Generic List wi

2020-06-23 08:29发布

问题:

I am looking the whole day for a proper solution, bit I am fairly new to C#. If I am right, want something similar to the Java code

ArrayList<IAnimalStuff<? extends Animal>> ianimals = new ArrayList<>();

just for C#. Or another solution when I am on the wrong way.

Detailed Scenario: I have a base class (Animal) and multiple subclasses (e.g. Dog).

class Animal
{
}
class Dog : Animal
{
}

I create a common list of all animals, which contains objects of all kinds of different animals.

List<Animal> animals = new List<Animal>();
animals.add(new Dog()); // and so on

Additionally, I have an interface and a class for each special animal derived from this interface.

interface IAnimalStuff<TAnimal> where TAnimal : Animal
{
    void doSomething(TAnimal animal);
}

public class DogStuff : IAnimalStuff<Dog>
{
    public override void doSomething(Dog animal) 
    {
    }
}

Now I want to manage one list with Animals and one list with AnimalStuff. When looping over all animals, I want to perform all Animalstuff in the other list which are valid for a dog. While a list of animals is no problem, I have my problems to create the other list.

List<IAnimalStuff<Animals>> ianimals = new List<IAnimalStuff<Animals>>();

Unlike in the first list, I only can add objects to this list of type

IAnimalStuff<Animals>

, but I also want to do

ianimals.add(GetDogStuff()); // add object of type IAnimalStuff<Dog>

I assumed this is valid, because Dog is a subclass of Animal. I think with the upper line of Java code this can be solved, but I did not find any solution for C#. Or am I on the wrong track?

回答1:

C# has declaration-site variance, not use-site variance like Java.

In C# you could do this:

interface IAnimalStuff<in TAnimal> where TAnimal : Animal // note "in"
{
    void doSomething(TAnimal animal);
}

And then you could say

IAnimalStuff<Mammal> iasm = new MammalStuff();
IAnimalStuff<Dog> iasd = iasm;

Why does this work? Because iasm.doSomething takes any mammal, and iasd.doSomething will be passed only dogs, and dogs are mammals. Notice that this is a contravariant conversion.

But you cannot go the other way; you can't say "dog is a mammal, therefore a dogstuff is a mammalstuff". That mammalstuff can accept a giraffe, but a dogstuff cannot. That would be a covariant conversion.



回答2:

I believe the issue might be in your declaration of your list:

List<IAnimalStuff<Animals>> ianimals = new List<IAnimalStuff<Animals>>();

This is casting the "animal" of AnimalStuff to type "Animal". Instead, try using an Interface as your base IAnimal and defining your collection as IAnimalStuff<IAnimals>. Then have Dog inherit from IAnimal and it should accomplish what you want.