IEnumerable IndexOutOfRangeException - PullRequest
       8

IEnumerable IndexOutOfRangeException

0 голосов
/ 30 сентября 2018

Я не знаю, почему я получаю 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);
}

Пожалуйста, кто-нибудь может объяснить, что не так с моим кодом.

Ответы [ 2 ]

0 голосов
/ 30 сентября 2018

Это происходит потому, что, хотя ваш цикл 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);
0 голосов
/ 30 сентября 2018

Проблема в том, что ваше лямбда-выражение захватывает переменную i, но делегат не выполняется до окончания цикла.Ко времени выполнения выражения c != illegals[i] значение i равно illegals.Length, поскольку это окончательное значение i.Важно понимать, что лямбда-выражения захватывают переменные, а не «значения этих переменных в точке, где лямбда-выражение преобразуется в делегат».

Вот пять способов исправить ваш код:

Вариант 1: локальная копия i

Скопируйте значение i в локальную переменную внутри цикла, чтобы каждая итерация цикла захватывала новую переменную влямбда-выражение.Эта новая переменная не изменяется во время выполнения цикла.

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

Вариант 2: извлекать нелегалов [i] вне лямбда-выражения

Извлеките значение illegals[i] в цикле (вне лямбда-выражения) и используйте это значение в лямбда-выражении.Опять же, изменяющееся значение i не влияет на переменную.

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

Опция 3: использовать цикл по каждому элементу

Эта опция корректно работает только сКомпиляторы C # 5 и более поздние, поскольку значение foreach изменилось (в лучшую сторону) в C # 5.

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

Вариант 4: использовать Except один раз

LINQ предоставляет метод для исключения набора: Except.Это , а не , совсем не то же самое, что более ранние опции, поскольку вы получите только одну копию любого конкретного символа в вашем выводе.Таким образом, если e не было в illegals, вы получите результат "Tex resul" с указанными выше параметрами, но "Tex rsul" с использованием Except.Тем не менее, стоит знать о:

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

Вариант 5: Использовать Contains один раз

Вы можете вызвать Where один раз, с лямбда-выражением, которое вызываетContains:

// Replace the loop entirely with this
query = query.Where(c => !illegals.Contains(c));
...