5

How to make a list hold all the different implementations of generic interface?

e.g

public class Animal { // some Animal implementations }
public class Dog : Animal { // some Dog implementations }
public class Snake : Animal { // some Snake implementations }

public interface ICatcher<T> where T: Animal
{
    // different animals can be caught different ways.
    string Catch(T animal);
}

public class DogCatcher : ICatcher<Dog> 
{ 
    string Catch(Dog animal) { // implementation }
}

public class SnakeCatcher : ICatcher<Snake> 
{ 
    string Catch(Snake animal) { // implementation }
}

I want to hold all the catcher in a list something like,

public class AnimalCatcher
{
     // this will hold the catching method an animal catcher knows (just something similar)
     public IEnumerable<ICatcher<Animal>> AnimalCatcher = new List<ICatcher<Animal>>
     {
          new DogCatcher(),
          new SnakeCatcher()
     }
}

I know that it is something to deal with generic modifiers in c# (Covariance, Contravariance and Invariance) but unable to get it working.

Tried: adding 'out' in

public interface ICatcher<out T> where T: Animal
{
    // different animals can be caught different ways.
    string Catch(T animal);
}

but gives a compile time error :

"The type parameter 'T' must be contravariantly valid on 'ICatcher.Catch(T)'. 'T' is covariant."

What am i doing wrong?

Canica
  • 2,650
  • 3
  • 18
  • 34
kartik rajan
  • 115
  • 1
  • 5
  • Did you try casting both the catchers (`new DogCatcher()` and `new SnakeCatcher()`) to `ICatcher` ? – Eric Wu Nov 11 '19 at 14:42
  • 2
    similar question is asked and well explained by Eric Lippert https://stackoverflow.com/questions/46893858/covariance-in-generics-creating-a-generic-list-with-a-bounded-wildcard – Farhad Jabiyev Nov 11 '19 at 14:47
  • 3
    How are you going to use this list ? – Fabjan Nov 11 '19 at 15:02
  • 1
    Possible duplicate of [How to create List of open generic type of class?](https://stackoverflow.com/questions/58570948/how-to-create-list-of-open-generic-type-of-classt) –  Nov 11 '19 at 15:21

4 Answers4

2

You need some type unification for animals, and you need to remove the generic declaration of ICatcher and make it a concrete type:

public interface IAnimal {}

public class Dog : IAnimal {}
public class Snake : IAnimal {}

public interface ICatcher
{
    // different animals can be caught different ways.
    string Catch(IAnimal animal);
}

Then you can have a collection of catchers like so:

public class AnimalCatcher
{
     // this will hold the catching method an animal catcher knows (just something similar)
     public IEnumerable<ICatcher> AnimalCatcher = new List<ICatcher>
     {
          new DogCatcher(),
          new SnakeCatcher()
     }
}

ETA: Here's a repl.it demonstrating how it would be set up with interfaces. Although, in the respective Catch implementations, you are going to have to cast the IAnimal interface (if you need to access instance variables specific to a particular animal implementation)

gabriel.hayes
  • 2,267
  • 12
  • 15
  • Removing the type specification from the interface is not always possible. – Eric Wu Nov 11 '19 at 14:45
  • 1
    @EricWu I don't see why not remove it, the primary concern seems to be having an array of various catchers. It's not going to be possible to have an array of various catchers if the catchers are defined generically. Though, even if they are implemented generically, they would still need some unifying concrete interface to be grouped into a collection. Thanks for the edit though – gabriel.hayes Nov 11 '19 at 14:50
  • Well, or a common ancestor that has a concrete type. (I suppose that would make more sense than an interface, in retrospect, since `Dog`s and `Snake`s do indeed descend from `Animal`s) – gabriel.hayes Nov 11 '19 at 14:51
  • To me, the point of the example code having a generic argument on the interface is to say "I have a DogCatcher. It knows how to catch animals, as long as that animal is a dog. It can't do anything for a snake." Generics enforce you don't accidently pass a snake in disguise (as an IAnimal). – gunr2171 Nov 11 '19 at 14:53
  • @gunr2171 that's a fair point. I'll work on a more type-safe solution. – gabriel.hayes Nov 11 '19 at 14:53
  • basically i want to loop over all the catchers and execute the catch method of each catcher with the type of instance it accepts, but with this approach i lose the capability of accessing the method at compile time. – kartik rajan Nov 17 '19 at 09:55
2

If Dog is Animal, it doesn't mean that ICatcher<Dog> is ICatcher<Animal>. There is no any relationship between ICatcher<Dog> and ICatcher<Animal>.

Microsoft made that decision to prevent run-time errors in case of contravariant T. Let's take a look:

ICatcher<Dog> dogCatcher = new DogCatcher();
ICatcher<Animal> animalCatcher = dogCatcher; 

// Then, we can pass `Snake` as input to the `animalCatcher` which is actually `DogCatcher`
animalCatcher.Catch(new Snake()); // ???

For saying that ICatcher<Dog> is ICatcher<Animal>, we have to use out keyword in front of T which will say that ICatcher<T> is covariant in T. And it means T will be used only as return type inside your interface . But, currently you are passing T as input to the Catch method.

Farhad Jabiyev
  • 26,014
  • 8
  • 72
  • 98
  • 2
    The example code doesn't actually showcase the current problem. You're never able to do `Derived d = new Base()` regardless of covariance and contravariance (barring implicit conversions). A fitting example would be to have the third line be `animalCatcher.Catch(new Snake())` (which shouldn't be possible since the catcher is actually a dog catcher) – Flater Nov 11 '19 at 15:26
0

As your question does not say what the List is used for, I am making a wild guess at the problem you are trying to solve.

I expect what you need is

public interface ICatcher
{
     void MethodThatMakesYouListUseful()
}

public interface IPerAnimalCatcher<T> : ICatcher where T: Animal
{
    // different animals can be caught different ways.
    string Catch(T animal);
}

AnimalCatchers = new List<ICatcher>();

As there is no useful type relationship between generic interfaces unless you define it yourself.

Ian Ringrose
  • 51,220
  • 55
  • 213
  • 317
0

Consider introducing an ICatcher interface. Then inherit it in your ICatcher<T>.

At the end create an IEnumerable<ICatcher> instead of IEnumerable<ICatcher<Animal>>.

Generics in Microsoft libraries have the same approach.

VJPPaz
  • 917
  • 7
  • 21