是否有规范的方法来“修复”“动态”IEnumerable?

时间:2012-02-09 09:33:05

标签: c# linq ienumerable

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的方法并且你想要一次只评估序列的属性(然后引用是&#34;固定&#34;)时,是什么?这样做的规范方法? ToList()似乎主要用于,但如果源已经修复(例如,如果源是一个数组),引用将被复制到一个列表(不必要的,因为我需要的只是固定属性)。是否有更合适的东西或ToList()&#34;规范&#34;这个问题的解决方案?

5 个答案:

答案 0 :(得分:3)

ToList绝对是最佳选择。

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

恕我直言,一般情况下,大多数方法最好返回ICollection<T>IList<T>而不是IEnumerable<T>,除非要提示给消费者实现可以使用延迟评估(例如,产量)的方法。

如果方法应返回不可变列表,则返回只读包装器(ReadOnlyCollection<T>),例如通过调用ToList().AsReadOnly(),但仍返回接口类型IList<T>

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

答案 1 :(得分:2)

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

答案 2 :(得分:2)

IEnumerable接口只是给出一种迭代元素的“方法”(从linq查询创建IEnumerable是一个很好的例子,因为IEnumerable就像数据库的SQL查询一样)。在将结果存储到ICollection(ToList,ToArray)之前,它始终是动态的,因此迭代将被处理直到结束并且结果以“固定”方式存储。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

一般来说,当接受一个IEnumerable<T>意味着不止一次地引用或操纵时,将序列复制到内部表示会更安全。我的偏好是使用ToList()扩展名,因为它非常容易理解,可能是最简单的可预测排序的集合。

在内部,只要需要“常规”集合,.NET Framework就会使用List<T>