【问题标题】:Is there a canonical way to "fix" a "dynamic" IEnumerable?是否有“修复”“动态”IEnumerable 的规范方法?
【发布时间】:2012-03-01 18:32:03
【问题描述】:

IEnumerable 不保证枚举两次会产生相同的结果。事实上,创建一个 myEnumerable.First() 在执行两次时返回不同值的示例非常容易:

class A {
    public A(string value) { Value = value; }
    public string Value {get; set; } 
}

static IEnumerable<A> getFixedIEnumerable() {
    return new A[] { new A("Hello"), new A("World") };
}

static IEnumerable<A> getDynamicIEnumerable() {
    yield return new A("Hello");
    yield return new A("World");
}

static void Main(string[] args)
{
    IEnumerable<A> fix = getFixedIEnumerable();
    IEnumerable<A> dyn = getDynamicIEnumerable();

    Console.WriteLine(fix.First() == fix.First()); // true
    Console.WriteLine(dyn.First() == dyn.First()); // false
}

这不仅仅是一个学术例子:使用流行的from ... in ... select new A(...) 将创建这种情况。这可能会导致意外行为:

fix.First().Value = "NEW";
Console.WriteLine(fix.First().Value); // prints NEW

dyn.First().Value = "NEW";
Console.WriteLine(dyn.First().Value); // prints Hello

我明白为什么会发生这种情况。我也知道这可以通过在 Enumerable 上执行 ToList() 或覆盖 A 类的 == 来解决。这不是我的问题。

问题是:当您编写一个采用任意 IEnumerable 的方法并且您希望序列只计算一次的属性(然后引用被“固定”)时,执行此操作的规范方法是什么? ToList() 似乎主要使用,但如果源已经固定(例如,如果源是数组),则引用将复制到列表中(不必要,因为我只需要固定属性)。有没有更合适的方法或者ToList() 是这个问题的“规范”解决方案?

【问题讨论】:

  • 根据定义,IEnumerable 不是说不能保证顺序吗?如果您的实现需要订单,您使用的是错误的数据类型,不是吗?您在与 IEnumerable 的合同中所要求的只是元素可以遍历 IE:正如您所说,列表更适合。

标签: c# linq ienumerable


【解决方案1】:

ToList 绝对是正确的选择。

它有一些优化:如果输入枚举是 ICollection(例如数组或列表),它将调用 ICollection.CopyTo 将集合复制到数组而不是实际枚举 - 所以成本不太可能很大除非收藏量很大。

恕我直言,一般来说,大多数方法最好返回 ICollection&lt;T&gt;IList&lt;T&gt; 而不是 IEnumerable&lt;T&gt;除非您想向消费者提示实现可能使用的方法惰性求值(例如产量)。

如果方法应该返回不可变列表,请返回只读包装器 (ReadOnlyCollection&lt;T&gt;),例如通过调用ToList().AsReadOnly(),但仍然返回接口类型IList&lt;T&gt;

如果您遵循此指南,则该方法的使用者将不再需要对 ToList 进行不必要的调用。

【讨论】:

    【解决方案2】:

    你的意思是如果你的函数有一个参数并且你想确保参数不是惰性的,因为你需要在方法中多次迭代它?在这种情况下,我通常将参数设为 ICollection,如果它是惰性的,则调用者有责任具体化可枚举。

    【讨论】:

      【解决方案3】:

      IEnumerable 接口只是提供“一种方法”来遍历元素(从 linq 查询创建 IEnumerable 是一个完美的示例,因为IEnumerable 就像数据库的 SQL 查询)。在将结果存储到 ICollection (ToList, ToArray) 之前,它始终是动态的,因此迭代一直处理到结束,结果以“固定”方式存储。

      【讨论】:

        【解决方案4】:

        Joel Spolsky 在他的Law of Leaky Abstractions 中指出了最常见的问题。在您的方法中有IEnumerable 参数,您期望一个可以枚举的对象,仅此而已。但是,不管它是“固定的”还是“动态的”,你都会关心传递的集合。你可以看到IEnumerable 的抽象被泄露了。没有适合所有情况的解决方案。在您的情况下,您可以传递List&lt;T&gt;T[](或您希望在方法中作为参数的实际类型的其他类型)而不是IEnumerable。最常见的建议是实现您的抽象并根据它设计您的代码。

        【讨论】:

          【解决方案5】:

          从您解释问题的方式来看,您似乎想多次枚举一个序列并每次都获得相同的顺序。这意味着您要么保留对序列的引用以稍后执行迭代,要么在同一方法中进行多次比较。

          一般来说,当接受一个IEnumerable&lt;T&gt; 时,您的意思是要保留对或多次操作的引用,将序列复制到内部表示会更安全。我的偏好是使用 ToList() 扩展,因为它易于理解,并且可能是具有可预测顺序的最简单集合。

          在内部,当需要“通用”集合时,.NET Framework 倾向于使用List&lt;T&gt;

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-01-07
            • 2017-03-05
            • 2012-05-01
            • 2018-05-23
            • 2012-05-14
            • 2014-07-28
            • 2023-03-31
            • 2021-10-22
            相关资源
            最近更新 更多