Когда перечисляются коллекции (IEnumerable)

Недавно я столкнулся со странной проблемой, когда у меня был метод, генерирующий IEnumerable коллекцию объектов. Этот метод содержал четыре оператора yield return, которые возвращали четыре объекта. Я присвоил результат переменной results, используя ключевое слово var.

var result = GenerateCollection().ToList();

Фактически это означало: List<MyType> result = GenerateCollection().

Я сделал простой цикл for для элементов этой коллекции. Что меня удивило, так это то, что коллекция перенумеровывалась для каждого обращения к списку (для каждого result[i]). Позже я использовал коллекцию result в запросе LINQ, который имел некоторые плохие результаты с точки зрения производительности из-за постоянного перенумерации коллекции.

Я решил проблему, приведя массив вместо списка.

Что заставляет меня задаться вопросом, когда перечисляются коллекции? Какие вызовы методов заставляют коллекции перенумеровываться?

РЕДАКТИРОВАТЬ: метод GenerateCollection() выглядел примерно так:

public static IEnumerable<MyType> GenerateCollection()
{
    var array = data.AsParallel(); //data is a simple collection of sublists of strings
    yield return new MyType("a", array.Where(x => x.Sublist.Count(y => y == 'a') == 0));
    yield return new MyType("b", array.Where(x => x.Sublist.Count(y => y == 'b') == 0));
    yield return new MyType("c", array.Where(x => x.Sublist.Count(y => y == 'c') == 0));
    yield return new MyType("d", array.Where(x => x.Sublist.Count(y => y == 'd') == 0));
}

person Igor Ševo    schedule 26.01.2014    source источник
comment
Пожалуйста, покажите реализацию метода GenerateCollection и ваш цикл для каждого цикла, который воспроизводит проблему   -  person Sergey Berezovskiy    schedule 27.01.2014
comment
Как выглядит MyType? Что он делает со вторым параметром конструктора?   -  person Sergey Berezovskiy    schedule 27.01.2014
comment
По сути, это кортеж строки и список строк.   -  person Igor Ševo    schedule 27.01.2014
comment
Как я уже, наверное, сто раз говорил о SO: если бы я мог рассказать людям только одну вещь о LINQ, это то, что выражение запроса создает запрос, а не результирующий набор. Вы составляете последовательность вопросов, а не последовательность ответов.   -  person Eric Lippert    schedule 27.01.2014


Ответы (3)


Вы получаете объекты, внутри которых есть запросы - это не какая-то последовательность значений array - его объекты итератора, которые не выполняются, когда вы передаете их конструктору MyType. Когда вы создаете список из MyType объектов

var result = GenerateCollection().ToList();

все экземпляры MyType выдаются и сохраняются в список, но если вы не выполнили итераторы в конструкторе MyType, то запросы не выполняются. И даже больше - они будут выполняться каждый раз заново, если вы вызовете какой-нибудь оператор, выполняющий запрос, например.

result[i].ArrayIterator.Count(); // first execution
foreach(var item in result[i].ArrayIterator) // second execution
    // ...

Это можно исправить, если передать результат выполнения запроса конструктору MyType:

yield return new MyType("a", array.Where(x => !x.Sublist.Contains('a')).ToList())

Теперь вы также передаете список элементов вместо итератора (вы можете использовать ToArray()). Запрос выполняется, когда вы получаете экземпляр MyType, и больше он выполняться не будет.

person Sergey Berezovskiy    schedule 26.01.2014
comment
Я бы также подумал, не имеет ли больше смысла перемещать этот ToList() внутрь конструктора MyType. - person svick; 27.01.2014

array.Where(x => x.Sublist.Count(y => y == 'a') == 0)

Этот фрагмент кода будет перечисляться каждый раз, когда вы обращаетесь к нему в MyType. Используйте ToList или ToArray, чтобы убедиться, что он перечисляется только один раз в том месте, где написан код.

person Euphoric    schedule 26.01.2014

коллекции, основанные на отложенном выполнении, перечисляются, как только вы их используете. Например, IEnumerable, IQueryable и т. д., а коллекции, основанные на немедленном выполнении, перечисляются, как только они создаются, например LIST.

person slash shogdhe    schedule 27.01.2014