2012-02-25 50 views
28

我写了这个扩展方法(编译):平展IEnumerable <IEnumerable <>>;了解泛型

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):这个问题我考虑的回答,但它提出了关于另外一个问题重载解析和类型约束。我把这个问题放在这里:Why aren't type constraints part of the method signature?

+1

您的编辑不起作用,因为您有太多的周围的enumerable。 'foo.Flatten ,int>();'应该工作。 – dlev 2012-02-25 02:20:53

回答

65

首先,你不需要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 ,int>(foo)' – BrokenGlass 2012-02-25 02:23:13

+2

@Daryl通用约束不被视为方法签名的一部分;为*方式更多*,请参阅此链接:http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx – dlev 2012-02-25 02:24:44

+1

@Daryl :不要 - 在这里肯定有学习曲线,这远远不是C#最容易理解的方面。只是试图掌握它,让你超过95%的其余已经;-) – BrokenGlass 2012-02-25 02:31:51

13

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<List<int>>可转换为IE<IE<int>>。这给了类型推理一些继续;它可以推断T是int,并且一切都很好。

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

相关问题