views:

522

answers:

5

I understand that, if S is a child class of T, then a List<S> is not a child of List<T>. Fine. But interfaces have a different paradigm: if Foo implements IFoo, then why is a List<Foo> not (an example of) a List<IFoo>?

As there can be no actual class IFoo, does this mean that I would always have to cast each element of the list when exposing a List<IFoo>? Or is this simply bad design and I have to define my own collection class ListOfIFoos to be able to work with them? Neither seem reasonable to me...

What would be the best way of exposing such a list, given that I am trying to program to interfaces? I am currently tending towards actually storing my List<Foo> internally as a List<IFoo>.

+3  A: 

MASSIVE EDIT You'll be able to do it with C# 4.0, but [thanks Jon]

You can get around it using ConvertAll:

public List<IFoo> IFoos()
{
    var x = new List<Foo>(); //Foo implements IFoo
    /* .. */
    return x.ConvertAll<IFoo>(f => f); //thanks Marc
}
David Kemp
Unless I've misunderstood it, no you won't. A list can be neither co- nor contra- variant, since it allows both "in" and "out" usage.
Marc Gravell
+1 for Marc's comment.
Jon Skeet
You'd also need to qualify the destination type: return x.ConvertAll<IFoo>(f=>f);
Marc Gravell
There is an extension method in System.Linq so you can doreturn x.Cast<IFoo>(); instead of return x.ConvertAll<IFoo>(f => f);
Anthony
+8  A: 

In your returning function, you have to make the list a list of interfaces, and when you create the object, make it as an object that implements it. Like this:

function List<IFoo> getList()
{
  List<IFoo> r = new List<IFoo>();
  for(int i=0;i<100;i++)
   r.Add(new Foo(i+15));

  return r;
}
Tom Ritter
+13  A: 

Your List<Foo> is not a subclass if List<IFoo> because you cannot store an MyOwnFoo object in it, which also happens to be an IFoo implementation. (Liskov substitution principle)

The idea of storing a List<IFoo> instead of a dedicated List<Foo> is OK. If you need casting the list's contents to it's implementation type, this probably means your interface is not appropriate.

xtofl
A: 

The simple answer is that List<Foo> is a different type to List<IFoo>, in the same way that DateTime is different to IPAddress, for example.

However, the fact that you have IFoo implies that collections of IFoo are expected to contain at least two implementations of IFoo (FooA, FooB, etc...) because if you expect there to only ever be one implmentation of IFoo, Foo, then the IFoo type is redundant.

So, if there is only ever going to be one derived type of an interface, forget the interface and save on the overhead. If there are two or more derived types of an interface then always use the interface type in collections/generic parameters.

If you find yourself writing thunking code then there's probably a design flaw somewhere.

Skizz

Skizz
There are other reasons for using interfaces than just if I am intending for there to be different implementations.
Joel in Gö
+10  A: 

Here's an example of why you can't do it:

// Suppose we could do this...
public List<IDisposable> GetDisposables()
{
    return new List<MemoryStream>();
}

// Then we could do this
List<IDisposable> disposables = GetDisposables();
disposables.Add(new Form());

At that point a list which was created to hold MemoryStreams now has a Form in it. Bad!

So basically, this restriction is present to maintain type safety. In C# 4 and .NET 4.0 there will be limited support for this (it's called variance) but it still won't support this particular scenario, for exactly the reasons given above.

Jon Skeet
I really don't understand this. The list was created to hold IDisposables, not MemoryStreams. You could do the same kind of thing with a List<Stream>, and the compiler let you. What's different about interfaces?
Robert Rossney
"the compiler *lets* you."
Robert Rossney
No, you couldn't do this with List<Stream>. Try returning a List<MemoryStream> as a List<Stream> - you'll see it fail.
Jon Skeet
I sure wish I'd saved the code I'd written this afternoon, because you're right, it doesn't compile, and it shouldn't. I wonder what I was thinking.
Robert Rossney