tags:

views:

173

answers:

5

I have two classes: a base class (Animal) and a class deriving from it (Cat).Base class contains one virtual method Play that takes List as input parameter.Something like this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication9
{
    class Animal
    {
        public virtual void Play(List<Animal> animal) { }
    }
    class Cat : Animal
    {
        public override void Play(List<Animal> animal)
        {
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Cat cat = new Cat();
            cat.Play(new List<Cat>());
        }
    }
}

When i compile the above program,i get the following error

    Error    2    Argument 1: cannot convert from 'System.Collections.Generic.List' to 'System.Collections.Generic.List'

Is there anyway to accomplish this?

+5  A: 

You're looking for generic collection covariance. Obviously, though, that feature is not supported by the version of C# that you're using.

You can work around it by using the Cast<T>() extension method. Be aware, though, that this will create a copy of your original list instead of passing the original as a different type:

cat.Play((new List<Cat>()).Cast<Animal>().ToList());
Justin Niessner
Dykam
@Dykam - Can you ellaborate please? The comment doesn't make much sense as is.
Justin Niessner
Well, as List both takes input, as well returns output, co- and contravariance is not possible. If you would be able do `List<Animal> list = new List<Dog>();` The following would crash the application: `list.Add(new Elephant());`.
Dykam
+1  A: 

use the extension method Cast()

so:

class Program
{
    static void Main(string[] args)
    {
        Cat cat = new Cat();
        cat.Play(new List<Cat>().Cast<Animal>());
    }
}

The reason for this is b/c .net 3.5 does not support covariance, but 4.0 does :)

Jose
The reason is off. `cat.Play(new List<Cat>());` would also not work in 4 with the existing code.
Anthony Pegram
+5  A: 

You could do a few things. One example is cast the elements of the list to Animal

Using your code:

cat.Play(new List<Cat>().Cast<Animal>().ToList());

Another is to make Animal generic, so cat.Play(new List<Cat>()); would work.

class Animal<T>
{
    public virtual void Play(List<T> animals) { }
}
class Cat : Animal<Cat>
{
    public override void Play(List<Cat> cats)
    {
    }
}

class Program
{
    static void Main(string[] args)
    {
        Cat cat = new Cat();
        cat.Play(new List<Cat>());
    }
}

One other method is to not make Animal generic, but the Play method and constrain that to T : Animal

class Animal
{
    public virtual void Play<T>(List<T> animals) where T : Animal { }
}
class Cat : Animal
{
    public override void Play<T>(List<T> animals) 
    {
    }
}

Finally, if you are on C# 4 and only need to enumerate over the list and not modify it, check Eric Lippert's answer on IEnumerable<Animal>.

Anthony Pegram
+1 for the generic trick
Scott Chamberlain
Also +1 for pointing out that you should probably use IEnumerable for the parameter type rather than List
Joel Coehoorn
I don't understand that last bit; why does the class Cat have a method with a type parameter named Cat? Isn't that extremely confusing, to have two types with the same name inside the same declaration?
Eric Lippert
@Eric, I was just being dumb.
Anthony Pegram
@Eric, with that said, it looks like the generic on the class rather than the method appears to be the best as far as making the method *only* take a `List<Cat>` at compile-time as opposed to a list of another child of `Animal`. Is there another method I haven't considered?
Anthony Pegram
The problem with putting the generic type on the class Animal is that now Play is not restricted to taking a list of Animals *at all*. It could take a list of strings simply by instantiating Animal<string>. Rather than playing games with the generic type system I think it is best to model the desired behaviour clearly. This approach seems to be making things less clear rather than more clear.
Eric Lippert
@Eric, point taken. Although I certainly could include a constraint on the class, that still wouldn't stop someone from saying `class Cat : Animal<Dog>`.
Anthony Pegram
Right. Even if you said class Animal<T> where T : Animal<T>, that does not prevent the scenario you just mentioned. You can say class Dog:Animal<Dog>{} and class Cat : Animal<Dog>, and you're all set; a Cat now can play with a list of Dogs. To represent "An animal can play with other animals of its own type" requires a higher-order type system than the CLR generic type system. I believe Haskell can do it, but C# cannot.
Eric Lippert
Well, that about settles it. I'm becoming a plumber.
Anthony Pegram
Plumbers make good money, and it is impossible to outsource plumbing to overseas companies. I'm much more comfortable working with electricity than water though. If I ever get sick of this compiler plumbing job maybe I'll do that.
Eric Lippert
+11  A: 

The reason you cannot do this is because a list is writable. Suppose it were legal, and see what goes wrong:

List<Cat> cats = new List<Cat>();
List<Animal> animals = cats; // Trouble brewing...
animals.Add(new Dog()); // hey, we just added a dog to a list of cats...
cats[0].Speak(); // Woof!

Well dog my cats, that is badness.

The feature you want is called "generic covariance" and it is supported in C# 4 for interfaces that are known to be safe. IEnumerable<T> does not have any way to write to the sequence, so it is safe.

class Animal    
{    
    public virtual void Play(IEnumerable<Animal> animals) { }    
}    
class Cat : Animal    
{    
    public override void Play(IEnumerable<Animal> animals) { }    
}    
class Program    
{    
    static void Main()    
    {    
        Cat cat = new Cat();    
        cat.Play(new List<Cat>());    
    }    
}  

That will work in C# 4 because List<Cat> is convertible to IEnumerable<Cat>, which is convertible to IEnumerable<Animal>. There is no way that Play can use IEnumerable<Animal> to add a dog to something that is actually a list of cats.

Eric Lippert
I'm missing how having Play being a member of Animal adds to the explanation. That is, Play taking an IEnumerable<Animal>. IMO it would be clearer if it just was Play() and the example was placed in void Main() using assignment.
Dykam
@Dykam: It does not. I was attempting to follow the code structure laid out by the original poster.
Eric Lippert
@Eric Lippert, Ah, I see. For some reason I didn't link the two snippets.
Dykam
A: 

Everyone mentions the cast method already. If you can not update to 4.0 a way to hide the cast is

class Cat : Animal
{
    public override void Play(List<Animal> animal)
    {
         Play((List<Cat>)animal);
    }
    public virtual void Play(List<Cat> animal)
    {
    }
}

This is the same trick IEnumable and IEnumarable<T> play for GetEnumerator

Scott Chamberlain