标题几乎概括了这个问题。我对此没有任何问题,我只是对这种设计选择背后的原因感到好奇。
3 回答
我猜?对不同提供商的简单性和兼容性。
与其他一些答案相反,这与延迟执行无关——这是一个重要的概念,但与问题无关。例如,我可以制作以下完全有效的方法:
public static IEnumerable<T> NotBuffered<T>(this IEnumerable<T> input)
{
return (IEnumerable<T>)input.ToList(); //not deferred
}
或者,我可以公开 a WhereEnumerable
,它的工作原理与 a 类似,IEnumerable
但具有以下属性:
WhereEnumerable data = source.Where(x=> x.Name == "Cheese"); //still deferred
print(data.First());
print(data.skipped); //Number of items that failed the test.
print(data.returned); //Number of items that passed the test.
可以想象,这可能很有用 - 正如所证明的那样 - 并且易于在基本的LinqToObjects实现中实现。然而,在LinqToSQL或LinqToMongo或LinqToOpenCL驱动程序中实现相同的功能可能相当困难甚至不可能。这可能会使代码在实现之间的可移植性降低,并增加实现者的复杂性。
例如,MongoDB 在服务器上运行查询(使用专门的查询语言),并且不会向用户提供这些统计信息。此外,对于诸如索引之类的概念,这些概念可能毫无意义,例如users.Where(user => user.ID = "{ID"}).First()
在索引上可能会在找到结果之前“跳过”0 条记录,即使它位于索引中的位置 100,412 或磁盘上的 40,231 或索引节点 431 上。这是一个 '简单的问题...
最后,您始终可以编写自己的 LINQ 方法,以使用此功能返回您自己的自定义类型,如果您愿意,或者通过输出“统计”对象和类似的重载来返回。对于后者的假设示例:
var stats = new WhereStats();
WhereEnumerable data = source.Where(x=> x.Name == "Cheese", stats);
print(data.First());
print(stats.skipped); //Number of items that failed the test.
print(stats.returned); //Number of items that passed the test.
编辑:键入 where 的示例(仅限概念证明):
using System;
using System.Collections.Generic;
using System.Linq;
namespace TypedWhereExample
{
class Program
{
static void Main(string[] args)
{
var data = Enumerable.Range(0, 1000);
var typedWhere1 = data.TypedWhere(x => x % 2 == 0);
var typedWhere2 = typedWhere1.TypedWhere(x => x % 3 == 0);
var result = typedWhere2.Take(10).ToList(); //Works like usual Linq
//But returns additional data
Console.WriteLine("Result: " + string.Join(",", result));
Console.WriteLine("Typed Where 1 Skipped: " + typedWhere1.Skipped);
Console.WriteLine("Typed Where 1 Returned: " + typedWhere1.Returned);
Console.WriteLine("Typed Where 2 Skipped: " + typedWhere2.Skipped);
Console.WriteLine("Typed Where 2 Returned: " + typedWhere2.Returned);
Console.ReadLine();
//Result: 0,6,12,18,24,30,36,42,48,54
//Typed Where 1 Skipped: 27
//Typed Where 1 Returned: 28
//Typed Where 2 Skipped: 18
//Typed Where 2 Returned: 10
}
}
public static class MyLINQ
{
public static TypedWhereEnumerable<T> TypedWhere<T>
(this IEnumerable<T> source, Func<T, bool> filter)
{
return new TypedWhereEnumerable<T>(source, filter);
}
}
public class TypedWhereEnumerable<T> : IEnumerable<T>
{
IEnumerable<T> source;
Func<T, bool> filter;
public int Skipped { get; private set; }
public int Returned { get; private set; }
public TypedWhereEnumerable(IEnumerable<T> source, Func<T, bool> filter)
{
this.source = source;
this.filter = filter;
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
foreach (var o in source)
if (filter(o)) { Returned++; yield return o; }
else Skipped++;
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
foreach (var o in source)
if (filter(o)) { Returned++; yield return o; }
else Skipped++;
}
}
}
IEnumerable
' 仅在枚举时产生结果。假设您有一个查询,例如:
someEnumerable
.Select(a=>new b(a))
.Filter(b=>b.someProp > 10)
.Select(b=>new c(b));
如果 LINQ 步骤的返回值包含急切评估的内容,例如 aList<T>
那么每个步骤都必须完全评估才能传递到下一步。对于大量输入,这可能意味着在执行该步骤时会出现明显的等待/滞后。
LINQ 查询返回一个惰性求值的IEnumerable<T>
. 查询是在枚举时执行的。即使您的来源IEnumerable<T>
有数百万条记录,上述查询也是即时的。
编辑:将 LINQ 查询视为为您的结果创建管道,而不是强制创建结果。枚举本质上是打开生成的管道并查看结果。
此外,IEnumerables 是 .NET 中序列的最高转换形式,例如
IList<T> :> ICollection<T> :> IEnumerable<T>
这为您提供了最灵活的界面。
为了确保我正确理解您的问题,我将使用一个示例:
采取这种方法:
public static IEnumerable<TSource> Where<TSource>
(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
我假设您的问题是:为什么它会返回IEnumerable<TSource>
而不是,例如,Enumerable.WhereEnumerableIterator<TSource>
?
请注意,上面的类型是返回对象的实际运行时类型,但该方法只是将其声明为IEnumerable<TSource>
.
答案是,不这样做几乎没有任何好处,但会有非零成本。当成本高于收益时,不要这样做。
为什么没有好处?
首先,因为周围会有很多WhereEnumerableIterator<TSource>
对象,它们仍然静态类型为IEnumerable<TSource>
. WhereEnumerableIterator<TSource>
结果,方法重载将不起作用,就像今天一样,如果想要优化.Where(...).Where(...)
序列,Where 方法必须尝试将其输入转换为 a 。出现这种情况有几个原因,其中之一就是这种模式:
IEnumerable<whatever> q = source;
if (!string.IsNullOrEmpty(searchText))
{
q = q.Where(item => item.Name.Contains(searchText));
}
if (startDate.HasValue)
{
// What is the runtime type of q? And what is its compiletime type?
q = q.Where(item => item.Date > startDate.Value);
}
非零成本包括维护和文档成本(如果您公开它,您将更难以更改您的实现,并且您必须记录它),以及增加用户的复杂性。