Why can't I pass a List<List<Foo>> to an IEnumerable<IEnumerable<Foo>>
Asked Answered
A

1

11

This code generates two compile time errors:

private void DoSomething()
{
    List<List<Foo>> myFoos = GetFoos();

    UseFoos(myFoos);
}

private void UseFoos(IEnumerable<IEnumerable<Foo>>)
{

}

The best overloaded method match for 'NameSpace.Class.UseFoos(System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<Foo>>)' has some invalid arguments

and

Argument 1: cannot convert from 'System.Collections.Generic.List<System.Collections.Generic.List<Foo>>' to 'System.Collections.Generic.IEnumerable<System.Collections.Generic.IEnumerable<Foo>>'

Casting to IEnumberable<List<Foo>> isn't a problem. What's different about casting the inner List component of the type that it fails?

Algeciras answered 21/12, 2011 at 15:49 Comment(4)
Are you pre-C# 4.0? #1280832 and https://mcmap.net/q/1015870/-upcasting-and-generic-lists may be relevant, if not exact dupes.Accentual
Are you sure you're using C# 4.0?Undertaking
@AakashM: Correct I'm using 3.5.Algeciras
@Accentual Looking at the 3.5 workarounds in those examples; unless/until I actually need to support the inner container being something other than a List<> I think I'll pass. The implementations all fall the uglyness and obviousness checks.Algeciras
H
19

EDIT: I've just realized that I haven't really answered the aspect of how to work around the limitation. Fortunately it's quite easy:

UseFoos(myFoos.Cast<IEnumerable<Foo>>());

That code compiles fine (when you've given the UseFoos parameter a name) under C# 4, which introduced generic covariance and contravariance for interfaces and delegates.

As a simpler example, this works in C# 4 but not in C# 3:

IEnumerable<string> strings = new List<string>();
IEnumerable<object> objects = strings;

Note that even in C# 4, classes aren't invariant, so this won't work:

// This won't work
List<string> strings = new List<string>();
List<object> objects = strings;

... and even for interfaces, it's only supported when it's safe:

// This won't work either
IList<string> strings = new List<string>();
IList<object> objects = strings;

The interface (or delegate) has to declare the variance of the type parameter itself, so if you look at the .NET 4 documentation for IEnumerable<T> you'll see it's declared as

public interface IEnumerable<out T>

where out declares the covariance in T.

Eric Lippert has a lot more about this in his blog category of covariance and contravariance.

Hankow answered 21/12, 2011 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.