Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Generics: wildcards

Tags:

I'm new to the c# world, and I'm trying to wrap my head around generics. Here is my current problem:

public Interface IAnimal{   string getType(); }  public Interface IAnimalGroomer<T> where T:IAnimal{   void groom(T); } 

Now I want to have a dictionary that contains these animal groomers. How do I do that? In java, I could do something like this:

HashMap<String,IAnimalGroomer<?>> groomers = new HashMap<>(); 

Edit: Here is an example of what I'm trying to do:

public class  Dog : IAnimal {     public string GetType()     {         return "DOG";     }      public void ClipNails() { } }  public class DogGroomer : IAnimalGroomer<Dog> {     public void Groom(Dog dog)     {         dog.ClipNails();     } }  public class Program {     private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>();      public void doSomething()     {        //THIS DOESN"T COMPILE!!!!         groomers.Add(new DogGroomer());     } } 

EDIT I think my intentions were unclear in the original post. My ultimate goal is to make an AnimalGroomerClinic that employs different types of IAnimalGroomers. Then animal owners can drop off animals at the clinic, and the clinic can decide which groomer should take care of the animal:

public class AnimalGroomerClinic {     public Dictionary<String, IAnimalGroomer> animalGroomers = new Dictionary<String,IAnimalGroomer>();      public void employGroomer(IAnimalGroomer groomer){        animalGroomers.add(groomer.getAnimalType(), groomer);     }     public void Groom(IAnimal animal){       animalGroomers[animal.getAnimalType()].Groom(animal);     } } 

I realize I could do this without using generics. But the generics allow me to write the IAnimalGroomer interface in such a way that it is tied (at compile time) to a specific instance of IAnimal. In addition, concrete classes of IAnimalGroomer don't need to cast their IAnimals all the time, since generics would force implementations to deal with one specific kind of animal. I have used this idiom before in Java, and I'm just wondering if there is a similar way to write it in C#.

Edit 2: Lots of interesting discussion. I'm accepting an answer that pointed me to dynamic dispatching in the comments.

like image 533
Kyle Avatar asked Oct 09 '13 21:10

Kyle


2 Answers

What you want is call site covariance, which is not a feature that C# supports. C# 4 and above support generic variance, but not call site variance.

However, that doesn't help you here. You want a dog groomer to be put in a list of animal groomers, but that can't work in C#. A dog groomer cannot be used in any context in which an animal groomer is needed because a dog groomer can only groom dogs but an animal groomer can also groom cats. That is, you want the interface to be covariant when it cannot be safely used in a covariant manner.

However your IAnimalGroomer<T> interface could be contravariant as it stands: an animal groomer can be used in a context in which a dog groomer is required, because an animal groomer can groom dogs. If you made IAnimalGroomer<T> contravariant by adding in to the declaration of T then you could put an IAnimalGroomer<IAnimal> into an IList<IAnimalGroomer<Dog>>.

For a more realistic example, think of IEnumerable<T> vs IComparer<T>. A sequence of dogs may be used as a sequence of animals; IEnumerable<T> is covariant. But a sequence of animals may not be used as a sequence of dogs; there could be a tiger in there.

By contrast, a comparer that compares animals may be used as a comparer of dogs; IComparer<T> is contravariant. But a comparer of dogs may not be used to compare animals; someone could try to compare two cats.

If that is still not clear then start by reading the FAQ:

http://blogs.msdn.com/b/csharpfaq/archive/2010/02/16/covariance-and-contravariance-faq.aspx

and then come back and ask more questions if you have them.

like image 168
Eric Lippert Avatar answered Nov 22 '22 17:11

Eric Lippert


There are two interfaces, IEnumerable and IEnumerable<T> which are close to what you are trying to accomplish. So you can have a dictionary like Dictionary<string,IEnumerable> which can contain as values IEnumerable<int>, IEnumerable<string>, etc. The trick here is to derive IAnimalGroomer<T> from IAnimalGroomer, a non generic interface.

EDIT:

As an example, per your request, after creating an interface called IAnimalGroomer with:

public interface IAnimalGroomer{ } 

, if you change the line that reads:

public interface IAnimalGroomer<T> where T:IAnimal{ 

to

public interface IAnimalGroomer<T> : IAnimalGroomer where T:IAnimal{ 

and the line that reads:

private List<IAnimalGroomer<IAnimal>> groomers = new List<IAnimalGroomer<IAnimal>>(); 

to

private List<IAnimalGroomer> groomers=new List<IAnimalGroomer>(); 

your code should compile and work.

like image 30
Boluc Papuccuoglu Avatar answered Nov 22 '22 18:11

Boluc Papuccuoglu