43

我写了这个扩展方法(编译):

public static IEnumerable<J> Flatten<T, J>(this IEnumerable<T> @this) 
                                           where T : IEnumerable<J>
{
    foreach (T t in @this)
        foreach (J j in t)
            yield return j;
}

下面的代码导致编译时错误(找不到合适的方法),为什么?

IEnumerable<IEnumerable<int>> foo = new int[2][];
var bar = foo.Flatten();

如果我实现如下扩展,则不会出现编译时错误:

public static IEnumerable<J> Flatten<J>(this IEnumerable<IEnumerable<J>> @this)
{
    foreach (IEnumerable<J> js in @this)
        foreach (J j in js)
            yield return j;
}

编辑(2):这个问题我认为已经回答,但它提出了另一个关于重载解决和类型约束的问题。我在这里提出的这个问题:为什么类型约束不是方法签名的一部分?

4

2 回答 2

93

首先,你不需要Flatten();该方法已经存在,并被调用SelectMany()。你可以像这样使用它:

IEnumerable<IEnumerable<int>> foo = new [] { new[] {1, 2}, new[] {3, 4} };
var bar = foo.SelectMany(x => x); // bar is {1, 2, 3, 4}

其次,您的第一次尝试不起作用,因为泛型类型推断仅基于方法的参数,而不是与方法关联的泛型约束。由于没有直接使用J泛型参数的参数,类型推理引擎无法猜测J应该是什么,因此不认为您的方法是候选方法。

看看如何SelectMany()解决这个问题很有启发性:它需要一个额外的Func<TSource, TResult>论点。这允许类型推理引擎确定这两种泛型类型,因为它们都仅基于提供给方法的参数可用。

于 2012-02-25T02:09:29.570 回答
21

dlev 的回答很好;我只是想我会添加更多信息。

具体来说,我注意到您正在尝试使用泛型在IEnumerable<T>. 在 C# 4 及更高版本中,IEnumerable<T>已经是协变的。

你的第二个例子说明了这一点。如果你有

List<List<int>> lists = whatever;
foreach(int x in lists.Flatten()) { ... }

那么类型推断将推断List<List<int>>可转换为IE<List<int>>List<int>可转换为IE<int>,因此,由于协方差,IE<List<int>>可转换为IE<IE<int>>。这使类型推断得以继续;它可以推断出T是int,一切都很好。

这在 C# 3 中不起作用。在没有协方差的世界中生活会有些困难,但您可以明智地使用Cast<T>扩展方法。

于 2012-02-25T05:17:53.577 回答