I don't know why I'm getting System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'
with this code
IEnumerable<char> query = "Text result";
string illegals = "abcet";
for (int i = 0; i < illegals.Length; i++)
{
query = query.Where(c => c != illegals[i]);
}
foreach (var item in query)
{
Console.Write(item);
}
Please can someone explain what's wrong with my code?
The problem is that your lambda expression is capturing the variable i
, but the delegate isn't being executed until after the loop. By the time the expression c != illegals[i]
is executed, i
is illegals.Length
, because that's the final value of i
. It's important to understand that lambda expressions capture variables, rather than "the values of those variables at the point of the lambda expression being converted into a delegate".
Here are five ways of fixing your code:
Option 1: local copy of i
Copy the value of i
into a local variable within the loop, so that each iteration of the loop captures a new variable in the lambda expression. That new variable isn't changed by the rest of the execution of the loop.
for (int i = 0; i < illegals.Length; i++)
{
int copy = i;
query = query.Where(c => c != illegals[copy]);
}
Option 2: extract illegals[i] outside the lambda expression
Extract the value of illegals[i]
in the loop (outside the lambda expression) and use that value in the lambda expression. Again, the changing value of i
doesn't affect the variable.
for (int i = 0; i < illegals.Length; i++)
{
char illegal = illegals[i];
query = query.Where(c => c != illegal);
}
Option 3: use a foreach loop
This option only works properly with C# 5 and later compilers, as the meaning of foreach
changed (for the better) in C# 5.
foreach (char illegal in illegals)
{
query = query.Where(c => c != illegal);
}
Option 4: use Except
once
LINQ provides a method to perform set exclusion: Except
. This is not quite the same as the earlier options though, as you'll only get a single copy of any particular character in your output. So if e
wasn't in illegals
, you'd get a result of "Tex resul" with the above options, but "Tex rsul" using Except
. Still, it's worth knowing about:
// Replace the loop entirely with this
query = query.Except(illegals);
Option 5: Use Contains
once
You can call Where
once, with a lambda expression that calls Contains
:
// Replace the loop entirely with this
query = query.Where(c => !illegals.Contains(c));