Я всегда предполагал, что если бы я использовал Select(x=> ...)
в контексте LINQ для объектов, то новая коллекция была бы немедленно создана и оставалась бы статической. Я не совсем уверен, ПОЧЕМУ Я принял это, и это очень плохое предположение, но я это сделал. Я часто использую .ToList()
в другом месте, но часто не в этом случае.
Этот код демонстрирует, что даже простой "Выбрать" подлежит отсроченному исполнению:
var random = new Random();
var animals = new[] { "cat", "dog", "mouse" };
var randomNumberOfAnimals = animals.Select(x => Math.Floor(random.NextDouble() * 100) + " " + x + "s");
foreach (var i in randomNumberOfAnimals)
{
testContextInstance.WriteLine("There are " + i);
}
foreach (var i in randomNumberOfAnimals)
{
testContextInstance.WriteLine("And now, there are " + i);
}
Это выводит следующее (случайная функция вызывается каждый раз, когда выполняется итерация коллекции):
There are 75 cats
There are 28 dogs
There are 62 mouses
And now, there are 78 cats
And now, there are 69 dogs
And now, there are 43 mouses
У меня есть много мест, где у меня есть IEnumerable<T>
как член класса. Часто результаты запроса LINQ присваиваются такому IEnumerable<T>
. Обычно для меня это не вызывает проблем, но я недавно нашел несколько мест в своем коде, где он представляет собой не только проблему с производительностью.
В попытке проверить места, где я совершил эту ошибку, я подумал, что могу проверить, был ли конкретный IEnumerable<T>
типом IQueryable
. Это, я думал, скажет мне, была ли коллекция "отложена" или нет. Оказывается, что счетчик, созданный оператором Select выше, имеет тип System.Linq.Enumerable+WhereSelectArrayIterator``[System.String,System.String]
, а не IQueryable
.
Я использовал Reflector, чтобы узнать, от чего этот интерфейс унаследован, и получается, что он не наследует ничего, что указывает на то, что это" LINQ 'вообще - так что нет способа протестировать на основе типа коллекции.
Я довольно счастливо теперь помещаю .ToArray()
всюду сейчас, но я хотел бы иметь механизм, чтобы убедиться, что эта проблема не произойдет в будущем. Visual Studio, похоже, знает, как это сделать, потому что она дает сообщение о том, что "расширение представления результатов будет оценивать коллекцию".
Лучшее, что я придумал, это:
bool deferred = !object.ReferenceEquals(randomNumberOfAnimals.First(),
randomNumberOfAnimals.First());
Изменить: Это работает только в том случае, если новый объект создается с помощью "Выбрать" и не является общим решением. Я не рекомендую это в любом случае! Это был маленький язык в щеку решения.