1

标题几乎概括了这个问题。我对此没有任何问题,我只是对这种设计选择背后的原因感到好奇。

4

3 回答 3

1

我猜?对不同提供商的简单性和兼容性。

与其他一些答案相反,这与延迟执行无关——这是一个重要的概念,但与问题无关。例如,我可以制作以下完全有效的方法:

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实现中实现。然而,在LinqToSQLLinqToMongoLinqToOpenCL驱动程序中实现相同的功能可能相当困难甚至不可能。这可能会使代码在实现之间的可移植性降低,并增加实现者的复杂性。

例如,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++;
        }
    }
}
于 2013-11-06T10:07:11.747 回答
-1

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>

这为您提供了最灵活的界面。

于 2013-11-06T09:31:52.167 回答
-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 方法必须尝试将其输入转换为 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);
}

非零成本包括维护和文档成本(如果您公开它,您将更难以更改您的实现,并且您必须记录它),以及增加用户的复杂性。

于 2013-11-06T09:07:41.447 回答