30

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?

3

5 Answers 5

60

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.

4
  • 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
    Sep 17, 2010 at 17:07
  • 1
    @Dykam: It does not. I was attempting to follow the code structure laid out by the original poster. Sep 17, 2010 at 18:08
  • Ah, I see. For some reason I didn't link the two snippets.
    – Dykam
    Sep 18, 2010 at 15:41
  • This is the first explanation I fully understand covariance from. "because a list is writable" - this is the key. I think most of the cases people need to do like this List<IFoo> = new List<FooImplementer>(); and they don't understand why it does not work - they use interface... They may not aware 1. that covariant interface must be the container, not the type parameter, 2. the existence of IReadOnlyList, for example - as it was in my case...
    – Feri
    Jul 7, 2021 at 14:49
15

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>.

9
  • Also +1 for pointing out that you should probably use IEnumerable for the parameter type rather than List Sep 15, 2010 at 19:05
  • 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? Sep 15, 2010 at 21:38
  • @Eric, I was just being dumb. Sep 15, 2010 at 23:33
  • 1
    Well, that about settles it. I'm becoming a plumber. Sep 16, 2010 at 0:05
  • 1
    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. Sep 16, 2010 at 6:07
12

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());
3
  • It is not supported by List at all, as it is an in&out generic.
    – Dykam
    Sep 15, 2010 at 19:02
  • @Dykam - Can you ellaborate please? The comment doesn't make much sense as is. Sep 15, 2010 at 19:13
  • 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
    Sep 15, 2010 at 20:54
3

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 :)

1
  • The reason is off. cat.Play(new List<Cat>()); would also not work in 4 with the existing code. Sep 15, 2010 at 19:05
2

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.