简体   繁体   中英

Is there any possibility that there is an error by modified closure in the following code?

It is my understanding that the following code is safe as the Invoke is synchronous so the index is always increased after the action but I'm getting a report of an ArgumentOutOfRangeException at the array.ElementAt(index) line.

array is an IEnumerable generated from a LINQ query just before this code so it cannot be modified.

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

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

    index++;
}

The only way I can imagine to get the ArgumentOutOfRangeException is if, somehow, index gets increased before the array is accessed.

Is this possible in any way?

Since array is lazy evaluated change in the collection could cause index to be out of range (ie removing elements from collection ) even if you've corrected code with cached local variable.

Or as pointed in comments condition in Select may return different results every time it is executed.

Note that if you have R# you would be getting "possible multiple enumerations" warning as array.Count() and array.ElementAt() both need to iterate over collection to achieve results. So you really re-executing Select multiple times over while loop.

Fix:

  • perform single iteration with foreach (preferable)
  • force evaluation of enumerable with ToList() call before performing iteration (which will also bring down O(n^2) complexity of loop to O(n) as Count and ElementAt will be O(1) instead of at least O(n) when used on IEnumerable<T> ).

As you understand from comments your query will be executed every time you call array.Count or array.ElementAt(index) .
Every execution can return different result it can be reason for your Exception.
Use .ToList() extension method which "materialize" result of query to List<T> , which can be safely used

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

Since your code simply looping result of query, consider using @Alexei's suggestion to loop enumeration only once

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

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

Yes this is called a closure.. just define local variable instead of using index.

One more suggestion is to use ToList in the end of linq query so that Select is not evaluated at every call of Count and ElementAt

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

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

    index++;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM