以下代码中是否存在修改后的闭包错误?

时间:2016-02-09 17:59:28

标签: c# .net exception closures

我的理解是,以下代码是安全的,因为Invoke是同步的,因此索引总是在操作后增加,但我在array.ElementAt(index)行收到ArgumentOutOfRangeException的报告。

数组是在此代码之前从LINQ查询生成的IEnumerable,因此无法修改。

IEnumerable array = collection.Select(() => .....);

while (index < array.Count())
{
    this.CurrentDispatcher.Invoke(new Action(() =>
    {
        ...
        object a = array.ElementAt(index)
        ...
    }), DispatcherPriority.Input);

    index++;
}

我可以想象获得ArgumentOutOfRangeException的唯一方法是,在访问数组之前,索引会以某种方式增加。

这有可能吗?

3 个答案:

答案 0 :(得分:3)

由于array是惰性的,因此即使您使用缓存的局部变量更正了代码,集合中的更改也可能导致索引超出范围(即从collection中删除元素)。 / p>

或者如Select中的注释条件所指示,每次执行时都会返回不同的结果。

请注意,如果您拥有R#,您将获得多次枚举&#34;警告为array.Count()array.ElementAt()都需要迭代收集以获得结果。所以你真的在Select循环上多次重复执行while

修复:

  • 使用foreach(首选)执行单次迭代
  • 在执行迭代之前强制使用ToList()调用对枚举进行评估(这也会使O(n)的{​​{1}}的O(n ^ 2)复杂度降低为CountElementAtIEnumerable<T>上使用时,将是O(1)而不是至少O(n)。

答案 1 :(得分:3)

根据您的评论,您每次拨打array.Countarray.ElementAt(index)时都会执行查询。
每次执行都可以返回不同的结果,这可能是您的例外情况的原因 使用.ToList()扩展方法将查询结果“具体化”到List<T>,可以安全使用

List array = collection.Select(() => .....).ToList();

由于您的代码只是循环查询结果,请考虑使用@Alexei的建议仅循环枚举

IEnumerable array = collection.Select(() => .....);

foreach(var item in array)
{
    this.CurrentDispatcher.Invoke(new Action(() =>
    {
        //...
        object a = item;
        //...
    }), DispatcherPriority.Input);
}

答案 2 :(得分:2)

是的,这被称为闭包..只需定义局部变量而不是使用索引。

还有一个建议是在linq查询结束时使用ToList,这样在每次调用Count和ElementAt时都不会评估Select

select productid, dispenserid from magazine where magazineid = 41659