扁平化 IEnumerable>;了解泛型
本文关键字:IEnumerable 泛型 了解 扁平化 | 更新日期: 2023-09-27 18:25:41
我写了这个扩展方法(编译(:
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(:我认为这个问题已经回答了,但它提出了另一个关于重载分辨率和类型约束的问题。我在这里提出的这个问题:为什么类型约束不是方法签名的一部分?
首先,你不需要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>
论证。这允许类型推断引擎确定这两种泛型类型,因为它们都仅基于提供给方法的参数可用。
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>
扩展方法。