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?

santosh singh
  • 27,666
  • 26
  • 83
  • 129
  • 3
    possible duplicate of [Casting a generic collection to base type](http://stackoverflow.com/questions/539287/casting-a-generic-collection-to-base-type) – finnw Sep 15 '10 at 18:52
  • 1
    First thing to do is change the List<> argument to an IEnumerable<> argument. – Joel Coehoorn Sep 15 '10 at 19:04
  • possible duplicate of [Convert List<> of derived class objects to List<> of base class objects](http://stackoverflow.com/questions/1817300/convert-list-of-derived-class-objects-to-list-of-base-class-objects) – Jørn Schou-Rode Mar 07 '13 at 14:50

5 Answers5

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.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • I'm missing how having Play being a member of Animal adds to the explanation. That is, Play taking an IEnumerable. IMO it would be clearer if it just was Play() and the example was placed in void Main() using assignment. – Dykam Sep 17 '10 at 17:07
  • 1
    @Dykam: It does not. I was attempting to follow the code structure laid out by the original poster. – Eric Lippert Sep 17 '10 at 18:08
  • Ah, I see. For some reason I didn't link the two snippets. – Dykam Sep 18 '10 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 = new List();` 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 07 '21 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>.

Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
  • Also +1 for pointing out that you should probably use IEnumerable for the parameter type rather than List – Joel Coehoorn Sep 15 '10 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? – Eric Lippert Sep 15 '10 at 21:38
  • @Eric, I was just being dumb. – Anthony Pegram Sep 15 '10 at 23:33
  • @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` at compile-time as opposed to a list of another child of `Animal`. Is there another method I haven't considered? – Anthony Pegram Sep 15 '10 at 23:40
  • 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. 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 Sep 15 '10 at 23:45
  • @Eric, point taken. Although I certainly could include a constraint on the class, that still wouldn't stop someone from saying `class Cat : Animal`. – Anthony Pegram Sep 15 '10 at 23:48
  • Right. Even if you said class Animal where T : Animal, that does not prevent the scenario you just mentioned. You can say class Dog:Animal{} and class Cat : Animal, 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 Sep 15 '10 at 23:51
  • 1
    Well, that about settles it. I'm becoming a plumber. – Anthony Pegram Sep 16 '10 at 00: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. – Eric Lippert Sep 16 '10 at 06: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());
Justin Niessner
  • 242,243
  • 40
  • 408
  • 536
  • It is not supported by List at all, as it is an in&out generic. – Dykam Sep 15 '10 at 19:02
  • @Dykam - Can you ellaborate please? The comment doesn't make much sense as is. – Justin Niessner Sep 15 '10 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 list = new List();` The following would crash the application: `list.Add(new Elephant());`. – Dykam Sep 15 '10 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 :)

Jose
  • 10,891
  • 19
  • 67
  • 89
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

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431