代码之家  ›  专栏  ›  技术社区  ›  Paul Hollingsworth

为什么Linq扩展方法不放在IEnumerator而不是IEnumerable上?

  •  11
  • Paul Hollingsworth  · 技术社区  · 14 年前

    有很多Linq算法只需要对输入进行一次传递,例如Select。

        var e = new[] { 1, 2, 3, 4, 5 }.GetEnumerator(); 
        e.Select(x => x * x); // Doesn't work 
    

    这意味着您不能在从“已打开”流读取数据的任何情况下使用Linq。

    简言之,我有一个“已经打开”的结果流,我可以将其转换为一个适当的一次性IEnumerator——但不幸的是,所有的下游代码都需要IEnumerator而不是IEnumerator,即使它只需要一个“pass”。

    i、 e.我想在各种不同的源(CSV文件、IDataReaders等)上“实现”这个返回类型:

    class TabularStream 
    { 
        Column[] Columns; 
        IEnumerator<object[]> RowStream; 
    }
    

    为了得到“列”,我必须已经打开了CSV文件,启动了SQL查询,或者别的什么。然后我可以返回一个“IEnumerator”,它的Dispose方法关闭资源—但是所有Linq操作都需要IEnumerable。

    据我所知,最好的解决方法是实现一个IEnumerable,它的GetEnumerator()方法返回一个也是唯一一个IEnumerator,如果有东西尝试两次执行GetEnumerator()调用,就会抛出一个错误。

    2 回复  |  直到 14 年前
        1
  •  14
  •   Jon Skeet    14 年前

    使用 IEnumerator<T> 在我看来,直截了当很少是个好主意。

    通常 IEnumerator<T>

    它还使得在linqto对象中执行一些优化几乎不可能,例如使用 Count 如果你真的要求 ICollection<T> 为它计数。

    OneShotEnumerable 这是一个合理的方法。

        2
  •  8
  •   Community CDub    5 年前

    当我 同意 Jon Skeet's answer ,我也遇到过一些案例 IEnumerator IEnumerable .

    我将从一个这样的例子开始,并描述我自己对这个问题的解决方案。

    示例:仅向前、不可倒带的数据库游标

    ESRI 用于访问地理数据库的API( ArcObjects 迭代器 . 但是没有一个能与 I可数 . 因此,如果您想以“.NET方式”包装该API,您有三个选项(我按以下顺序进行了探讨):

    1. 将光标包装为 迭代器 (因为这才是真正的情况)并直接处理它(这很麻烦)。

    2. 环绕光标,或环绕 从(1),作为一个只有一次 I可数 (使其与LINQ兼容,并且通常更易于使用)。这里的错误是 不是吗 ,因为它不能被多次枚举,这可能会被代码的用户或维护人员忽略。

    3. 不要包装 光标本身作为 (例如,查询条件和对所查询数据库对象的引用)。这样,只需重新执行整个查询就可以进行多次迭代。这是我当时最终决定的。


    IEnumerator<T> 接口?

    接口。一种方法是编写一系列扩展方法,例如:

    public static IEnumerator<T> Where(this IEnumerator<T> xs, Func<T, bool> predicate)
    {
        while (xs.MoveNext())
        {
            T x = xs.Current;
            if (predicate(x)) yield return x;
        }
        yield break;
    }
    

    让我们考虑几个关键问题:

    • 操作员不得返回 IEnumerable<T> ,因为这意味着你可以突破自己的“林克到” “世界和逃入常规林克。在这里,您将得到上面已经描述的不可重复性问题。

    • 不能使用 foreach 循环,除非 IEnumerator<T> GetEnumerator 返回的方法 this . 提供额外的方法意味着您不能使用 yield return/break IEnumerator<T>

      这实在是太奇怪了,而且可能是对两者的滥用 或者 foreach公司 构造。

    • 如果返回 IEnumerable<T> 禁止返回 IEnumerator<T> 很麻烦(因为 foreach公司


    IQueryable + = IQueryator

    在完全组合查询之前延迟执行查询怎么样?在 世界,那是什么 所以理论上我们可以建立一个 迭代器 我称之为等价物 液化器 .

    • 液化器 可以检查逻辑错误,例如在序列被前面的操作(如)完全消耗之后对其执行任何操作 Count . 即所有消耗运算符 计数 总是必须是查询运算符连接中的最后一个。

    • 液化器 可以返回数组(如上面所建议的)或其他只读集合,但不能由单个运算符返回;只有在执行查询时才返回。

    实施 液化器