标题几乎总结了这个问题。我对此没有任何问题,我只是对设计选择背后的原因感到好奇。
答案 0 :(得分:1)
我的猜测?不同提供商的简单性和兼容性。
与其他一些答案相反,这与延迟执行无关 - 这是一个重要的概念,但与问题无关。例如,我可以制作以下完全有效的方法:
public static IEnumerable<T> NotBuffered<T>(this IEnumerable<T> input)
{
return (IEnumerable<T>)input.ToList(); //not deferred
}
或者,我可以公开一个WhereEnumerable
,它就像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上。这是一个“简单”的问题...
最后,如果您希望或通过输出“stats”对象等的重载,您始终可以编写自己的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.
编辑:键入的位置示例(仅限概念证明):
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++;
}
}
}
答案 1 :(得分:-1)
为了确保我正确理解你的问题,我将使用一个例子:
采取这种方法:
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(...)
。出现这种情况有几个原因,其中一个就是这种模式:
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);
}
非零成本由维护和文档成本组成(如果您公开它,则需要更改实现,并且必须对其进行记录),并增加用户的复杂性。
答案 2 :(得分:-1)
IEnumerable
仅在枚举时产生结果。假设您有一个查询,例如:
someEnumerable
.Select(a=>new b(a))
.Filter(b=>b.someProp > 10)
.Select(b=>new c(b));
如果LINQ的返回值是一个热切评估内容的东西,例如List<T>
,那么每个步骤都必须完全评估才能传递给下一个。对于大输入,这可能意味着在执行该步骤时会出现明显的等待/延迟。
LINQ查询返回一个延迟评估的IEnumerable<T>
。查询在枚举时执行。即使你的来源IEnumerable<T>
有数百万条记录,上述查询也是即时的。
编辑:将LINQ查询视为为结果创建管道,而不是命令性地创建结果。枚举基本上是打开生成的管道,看看会发生什么。
此外,IEnumerables是.NET中序列最常用的形式,例如
IList<T> :> ICollection<T> :> IEnumerable<T>
这为您提供了最灵活的界面。