为什么很多Linq方法返回IEnumerable而不是更具体的类型?

时间:2013-11-06 08:51:42

标签: c# linq

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

3 个答案:

答案 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>

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