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

Dar*_*ryl 35 c# generics ienumerable nested-generics

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

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;
}
Run Code Online (Sandbox Code Playgroud)

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

IEnumerable<IEnumerable<int>> foo = new int[2][];
var bar = foo.Flatten();
Run Code Online (Sandbox Code Playgroud)

如果我实现如下扩展,我没有编译时错误:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

dle*_*lev 81

首先,你不需要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}
Run Code Online (Sandbox Code Playgroud)

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

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

  • @Daryl通用约束不被视为方法签名的一部分; for*way more*,请看这个链接:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx (2认同)
  • 在过去的几年里,我为各种项目编写了类似"Flatten"方法的东西,我也一直称它为"Flatten".即使现在我知道它存在,我认为"SelectMany"对于该方法的名称是一个令人难以置信的误导性选择,因为假设称为"SelectMany"的东西会使层次结构变平是非常违反直觉的.+1指出它实际上是. (2认同)

Eri*_*ert 16

dlev的答案很好; 我只是想添加一些信息.

具体来说,我注意到您正在尝试使用泛型来实现一种协方差IEnumerable<T>.在C#4及以上版本中,IEnumerable<T>已经是协变的.

你的第二个例子就是这个例 如果你有

List<List<int>> lists = whatever;
foreach(int x in lists.Flatten()) { ... }
Run Code Online (Sandbox Code Playgroud)

然后类型推断将推断List<List<int>>可转换为IE<List<int>>,List<int>可转换为IE<int>,因此,因为协方差,IE<List<int>>可转换为IE<IE<int>>.这给了类型推断一些事情; 它可以推断T是int,一切都很好.

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