0

I have a bunch of lists of objects that implement an interface I. I want to make a List of these Lists and work on them as a singular object. I've tried making a List<List<I>> and adding my other lists to it, which in my mind makes sense since the lists I am adding contain objects that implement I, but the compiler isn't letting me do this.

Example:

public interface I
{
 //stuff
}

public class A : I
{
 //...
}
public class B : I
{
 //...
}

//elsewhere
public class C
{
    List<A> ListOfA; 
    List<B> ListOfB; 
    List<List<I>> ListOfLists;
    public C()
    {
        ListOfA = new List<A>();
        ListOfB = new List<B>();
        ListOfLists = new List<List<I>>();
        ListOfLists.Add(ListOfA); //nope
        ListOfLists.Add(ListOfB); //nope
    }
}

I have two questions, the first being why is this not allowed behaviour? The second is how can I accomplish what I'm looking for?

For clarification, when I try to add the lists to ListOfLists I get "cannot convert from List to List" where T : I. I'm using .NET 3.5, and no I cannot upgrade to a later version.

ldam
  • 4,412
  • 6
  • 45
  • 76
  • This is about variance and contrivance and what you want is not possible in your way – Alireza Sep 22 '14 at 13:34
  • Also related: ["In C#, why can't a List object be stored in a List variable"](http://stackoverflow.com/questions/6557/in-c-why-cant-a-liststring-object-be-stored-in-a-listobject-variable). – vgru Sep 22 '14 at 13:39

2 Answers2

1

If you could, you would be able to write:

List<I> listOfAs = ListOfLists[0];

var B = new B();
listOfAs.Add(B);

Which is obviously forbidden as you cannot add a B instance to a list of A.

This answer by Eric Lippert gives a good similar example with animals instead of As and Bs.

To answer your second question, we'll need to do what you intend to do with ListOfLists, simply enumerate or modify the collection after it has been initialized.

If the idea is just to have a read only collection of I instances:

IEnumerable<I> collectionOfIs = ListOfA.Cast<I>().Concat(ListOfB.Cast<I>());

which works because List implements IEnumerable

Then you can use ToList to materialize the collection and be able to use an indexer:

List<I> listOfIs = collectionOfIs.ToList();
Community
  • 1
  • 1
vc 74
  • 37,131
  • 7
  • 73
  • 89
  • Good point, but what if I'd like a read only, concatenated List containing the elements of all the other lists? – ldam Sep 22 '14 at 13:27
  • 3
    @Logan: Then the type you want is `IEnumerable`, not `List`. – Eric Lippert Sep 22 '14 at 13:29
  • Aha! I missed the `Concat` method. Let me try that quickly. – ldam Sep 22 '14 at 13:31
  • @Logan You'll need to a reference to System.Linq in your usings – vc 74 Sep 22 '14 at 13:32
  • Thanks, this is what I asked for, but using `IEnumberable` means I lost access to my indexers :( which I kind of need. – ldam Sep 22 '14 at 13:39
  • @Logan: if this works, I don't see why you don't simply copy all items into a single list (because that's essentially what `Concat` does). – vgru Sep 22 '14 at 13:42
  • @Groo I'm just worried about performance implications of that... I think I'm going to need to change a lot of stuff to get what I actually want, this is a design problem more than anything. – ldam Sep 22 '14 at 13:43
  • 1
    @Logan: if you don't want to switch to .NET 4.5, and you don't want to copy, then you will have to write your own code, or consider using something like this: [IIndexable: A read-only list interface](http://www.codeproject.com/Articles/233738/IIndexable-a-true-read-only-list-interface) (disclaimer: I wrote that). It has lightweight readonly wrapper classes which can project elements on the fly (i.e. cast them when you index them, without creating a new list). I.e. in this case you would use `ListOfA.AsIndexable().Select(a => (I)a)` and it would return the indexable readonly collection. – vgru Sep 22 '14 at 13:49
  • @Logan, you can copy all the Is to a List, I've updated my answer. – vc 74 Sep 22 '14 at 14:03
  • Turns out I don't need the indexers, so I switched it to `IEnumberable` and switched my `for` loop to a `foreach` and it's working fine. I'm using the `Cast` and `Concat` methods as you suggested. – ldam Sep 22 '14 at 14:20
  • @Groo very nice, I might use that in future. Thanks. – ldam Sep 22 '14 at 14:24
0

You need to use IEnumerable<I> because List<I> is a collection object

    List<A> ListOfA;
    List<B> ListOfB;
    List<IEnumerable<I>> ListOfLists;
    public C()
    {
        ListOfA = new List<A>();
        ListOfB = new List<B>();
        ListOfLists = new List<IEnumerable<I>>();
        ListOfLists.Add(ListOfA);
        ListOfLists.Add(ListOfB);
    }
Sam
  • 2,950
  • 1
  • 18
  • 26
  • Tried this, doesn't work. Same problem, cannot convert from List to IEnumberable – ldam Sep 22 '14 at 13:28
  • @Logan dude i tried this too :|, post your attempt – Sam Sep 22 '14 at 13:28
  • 1
    @Logan: this should work in .NET 4.5 and newer because the generic type parameter for `IEnumerable` was made [covariant](http://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx) (i.e. the `out` keyword was added: `IEnumerable`). – vgru Sep 22 '14 at 13:30
  • This works for me aswell – Pavenhimself Sep 22 '14 at 13:33
  • I'm using .NET 3.5, see my tags :) I'll update the question to specify though. – ldam Sep 22 '14 at 13:40
  • In that case you can add `.Cast()` to `ListOfA` and `ListOfB` when adding them to `ListOfLists`. – vgru Sep 22 '14 at 13:43