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:
foreach
(preferable) 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.