views:

406

answers:

2

How can I make the following code work? I don't think I quite understand C# generics. Perhaps, someone can point me in the right direction.

    public abstract class A
    {
    }

    public class B : A
    {
    }

    public class C : A
    {
    }

    public static List<C> GetCList()
    {
        return new List<C>();
    }

    static void Main(string[] args)
    {
        List<A> listA = new List<A>();

        listA.Add(new B());
        listA.Add(new C());

        // Compiler cannot implicitly convert
        List<A> listB = new List<B>();

        // Compiler cannot implicitly convert
        List<A> listC = GetCList();

        // However, copying each element is fine
        // It has something to do with generics (I think)
        List<B> listD = new List<B>();
        foreach (B b in listD)
        {
            listB.Add(b);
        }
    }

It's probably a simple answer.

Update: First, this is not possible in C# 3.0, but will be possible in C# 4.0.

To get it running in C# 3.0, which is just a workaround until 4.0, use the following:

        // Compiler is happy
        List<A> listB = new List<B>().OfType<A>().ToList();

        // Compiler is happy
        List<A> listC = GetCList().OfType<A>().ToList();
+2  A: 

you could always do this

List<A> testme = new List<B>().OfType<A>().ToList();

As "Bojan Resnik" pointed out, you could also do...

List<A> testme = new List<B>().Cast<A>().ToList();

A difference to note is that Cast<T>() will fail if one or more of the types does not match. Where OfType<T>() will return an IEnumerable<T> containing only the objects that are convertible

Matthew Whited
Thanks. That makes it possible in C# 3.0. However, is there any extra processing/overhead with the extra calls?
daub815
Yes ... but if you were to make it IEnumerable or IQueryable it wouldn't be as bad.
Matthew Whited
oftype requires you to enumerate the list though doesn't it? If you have contravariance then method calls can be bound by the type system not via the enum and will be significantly faster provided you don't need the cast?
Spence
Yes but it looks like he was asking how he could do this in C# 3.0/.Net 3.5
Matthew Whited
The compiler seemed to like it and it didn't throw a run time error. So I guess that oftype doesn't require an enumerator. It would be faster to have contravariance than to use the above calls, but C# 3.0 doesn't support it now. So it's a workaround for now.
daub815
Also keep in mind you aren't really moving too much around in memory. Assuming the list is rather short... this will not be a bottle neck or point of contention in your code.
Matthew Whited
Also, OfType<A> might be more suitable when you need to filter some elements from the list. CastTo<A> would more closely match the intent of the code.
Bojan Resnik
+4  A: 

The reason this does not work is because it cannot be determined to be safe. Suppose you have

List<Giraffe> giraffes = new List<Giraffe>();
List<Animal> animals = giraffes; // suppose this were legal.
// animals is now a reference to a list of giraffes, 
// but the type system doesn't know that.
// You can put a turtle into a list of animals...
animals.Add(new Turtle());

And hey, you just put a turtle into a list of giraffes, and the type system integrity has now been violated. That's why this is illegal.

The key here is that "animals" and "giraffes" refer to the SAME OBJECT, and that object is a list of giraffes. But a list of giraffes cannot do as much as a list of animals can do; in particular, it cannot contain a turtle.

Eric Lippert
I guess I just don't understand why it is illegal to be placing B and C into their abstract base class.
daub815
I have trouble to understand this as well:you put a Turtle in an animal list that thusfar happened only to contain giraffes. Why should that be a problem? Both Turtles and Giraffes will share the Animal members, right? The compiler should complain when you try to cast the animal list to a giraffe list.
Dabblernl
This would be different than just trying to get a collection of animals so you can call the .Walk() abstract method on all of them. If you want to do common work and not mix the types it's not an issue. Which is why it is probably best to just create a new list or to only use the list as IEnumerable<>
Matthew Whited
@Dabblernl: first you have a list of giraffes, say in variable L1. Then you have the same list as a list of animals, say in variable L2. You do L2.Add(new Turtle()). That added it to the list itself, so the list that both L1 and L2 point at has a turtle at the end. Now you do L1[32].LongNeck(), which the compiler thinks should be ok: L1 is a list of giraffes. But, item 32 is that turtle, which doesn't have a long neck. So, you get a runtime error that shouldn't happen according to the guarantees that the C# type system makes.
Zach Snow
@Dabblernl: Because giraffes is a list of giraffes. Making a new reference to it doesn't change the type!
mquander
@zacharyrsnow: I got it, L1 and L2 are just references to the same list. Thanks
Dabblernl