IEnumerable IndexOutOfRangeException异常

IEnumerable IndexOutOfRangeException

我不知道为什么我使用此代码 System.IndexOutOfRangeException: 'Index was outside the bounds of the array.'

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);
}

谁能解释一下我的代码有什么问题。

问题是您的 lambda 表达式正在捕获 变量i,但未执行 委托直到循环之后。在执行表达式 c != illegals[i] 时,iillegals.Length,因为那是 i 的最终值。重要的是要了解 lambda 表达式捕获变量,而不是 "the values of those variables at the point of the lambda expression being converted into a delegate".

这里有五种修复代码的方法:

选项 1:i 的本地副本

i 的值复制到循环内的局部变量中,以便循环的每次迭代都在 lambda 表达式中捕获一个新变量。循环的其余执行不会更改该新变量。

for (int i = 0; i < illegals.Length; i++)
{
    int copy = i;
    query = query.Where(c => c != illegals[copy]);
}

选项 2:在 lambda 表达式之外提取非法 [i]

在循环中(在 lambda 表达式之外)提取 illegals[i] 的值,并在 lambda 表达式中使用该值。同样,i 的变化值不会影响变量。

for (int i = 0; i < illegals.Length; i++)
{
    char illegal = illegals[i];
    query = query.Where(c => c != illegal);
}

选项 3:使用 foreach 循环

此选项仅适用于 C# 5 及更高版本的编译器,因为 foreach 的含义在 C# 5 中发生了变化(变得更好)。

foreach (char illegal in illegals)
{
    query = query.Where(c => c != illegal);
}

选项 4:使用 Except 一次

LINQ 提供了一种执行集合排除的方法:Except。这与之前的选项 完全相同,因为您只会在输出中获得任何特定字符的 单个 副本。因此,如果 e 不在 illegals 中,您将使用上述选项得到 "Tex resul" 的结果,但使用 Except 会得到 "Tex rsul" 的结果。不过,值得了解的是:

// Replace the loop entirely with this
query = query.Except(illegals);

选项 5:使用 Contains 一次

您可以调用 Where 一次,使用调用 Contains:

的 lambda 表达式
// Replace the loop entirely with this
query = query.Where(c => !illegals.Contains(c));

发生这种情况是因为,虽然您的 for 循环乍一看似乎是正确界定的,但每次迭代都会捕获传递给 Where 的闭包中的索引。闭包最有用的特性之一是它们通过引用进行捕获,从而实现各种强大而复杂的技术。但是,在这种情况下,这意味着,在随后的 foreach 循环中执行查询时。索引已递增超过数组的长度。

解决此问题的最直接更改是创建一个循环范围的复制索引循环控制变量的当前值,并在您的闭包中引用它,而不是直接引用循环控制变量。

例如:

for (int i = 0; i < illegals.Length; i++)
{
    var index = i;
    query = query.Where(c => c != illegals[index]);
}

然而,正如其他人所指出的,有更好的方法可以完全避免问题,并且它们还具有提高抽象级别的优点。

例如,您可以使用 System.Linq.Enumerable.Except

var legals = query.Except(illegals);