展开IEnumerable<IEnumerable<>>;理解泛型

44

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

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):我认为这个问题已经得到了解答,但它引发了另一个关于重载决议和类型约束的问题。我在这里发布了这个问题:为什么类型约束不是方法签名的一部分?


1
你的编辑不起作用,因为你有太多周围的可枚举对象。foo.Flatten<IEnumerable<int>, int>(); 应该可以解决问题。 - dlev
2个回答

95

首先,您不需要使用 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> 参数。这允许类型推断引擎确定两个泛型类型,因为它们都仅基于提供给该方法的参数可用。


1
@Daryl:因为应该是 Flatten<IEnumerable<int>,int>(foo) - BrokenGlass
2
@Daryl 泛型约束不被视为方法签名的一部分;如需更多信息,请参见此链接:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx - dlev
1
@Daryl:不要放弃——学习这个确实有一定的难度,这绝对不是 C# 中最容易理解的方面。只要尝试掌握它,你已经领先于其他 95% 的人了;-) - BrokenGlass
2
在过去的几年中,我为各种项目编写了类似于“Flatten”方法的东西,而且一直都称它为“Flatten”。即使现在我知道它的存在,我认为选择“SelectMany”作为该方法名称是极具误导性的选择,因为假设称为“SelectMany”的东西会展开层次结构是相当不直观的。赞成指出它实际上确实这样做。 - O. R. Mapper

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>扩展方法来解决问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接