Это известная ловушка для людей, которые промокают ноги с помощью LINQ:
public class Program
{
public static void Main()
{
IEnumerable<Record> originalCollection = GenerateRecords(new[] {"Jesse"});
var newCollection = new List<Record>(originalCollection);
Console.WriteLine(ContainTheSameSingleObject(originalCollection, newCollection));
}
private static IEnumerable<Record> GenerateRecords(string[] listOfNames)
{
return listOfNames.Select(x => new Record(Guid.NewGuid(), x));
}
private static bool ContainTheSameSingleObject(IEnumerable<Record>
originalCollection, List<Record> newCollection)
{
return originalCollection.Count() == 1 && newCollection.Count() == 1 &&
originalCollection.Single().Id == newCollection.Single().Id;
}
private class Record
{
public Guid Id { get; }
public string SomeValue { get; }
public Record(Guid id, string someValue)
{
Id = id;
SomeValue = someValue;
}
}
}
Это выведет «False», потому что для каждого имени, предоставленного для создания исходной коллекции, функция select продолжает переоцениваться, и результирующий Record
объект создается заново. Чтобы это исправить, ToList
в конце можно добавить простой вызов GenerateRecords
.
Какое преимущество Microsoft надеется получить, внедрив его таким образом?
Почему бы реализации просто не кэшировать результаты во внутренний массив? Одна конкретная часть того, что происходит, может быть отложенным выполнением, но это все еще может быть реализовано без этого поведения.
Как только данный элемент коллекции, возвращенный LINQ, был оценен, какое преимущество дает то, что вы не сохраняете внутреннюю ссылку / копию, а вместо этого пересчитываете тот же результат, что и поведение по умолчанию?
В ситуациях, когда существует особая потребность в логике для одного и того же члена коллекции, пересчитываемой снова и снова, кажется, что это может быть задано с помощью необязательного параметра, и поведение по умолчанию может действовать иначе. Кроме того, преимущество в скорости, получаемое с помощью отложенного выполнения, в конечном итоге сокращается на время, необходимое для постоянного пересчета одних и тех же результатов. Наконец, это запутанный блок для тех, кто новичок в LINQ, и это может привести к незначительным ошибкам в конечном итоге в любой программе.
В чем здесь преимущество, и почему Microsoft приняла это, казалось бы, очень обдуманное решение?
источник
return listOfNames.Select(x => new Record(Guid.NewGuid(), x)).ToList();
Это дает вам «кэшированную копию». Проблема решена.Ответы:
Кэширование результатов просто не будет работать для всех. Пока у вас есть крошечные объемы данных, отлично. Повезло тебе. Но что, если ваши данные больше вашей оперативной памяти?
Это не имеет ничего общего с LINQ, но с
IEnumerable<T>
интерфейсом в целом.Это разница между File.ReadAllLines и File.ReadLines . Один будет считывать весь файл в ОЗУ, а другой будет передавать его вам построчно, чтобы вы могли работать с большими файлами (если они имеют разрывы строк).
Вы можете легко кэш все , что вы хотите кэш материализуя вашу последовательность призывающей либо
.ToList()
или.ToArray()
на нем. Но те из нас, кто не хочет его кэшировать, у нас есть шанс не делать этого.И на заметку: как вы кешируете следующее?
Тебе нельзя. Вот почему
IEnumerable<T>
существует так, как есть.источник
int i=1; while(true) { i++; yield fib(i); }
Enumerable.Range(1,int.MaxValue)
- очень легко определить нижнюю границу того, сколько памяти будет использоваться.while (true) return ...
состояла в том,while (true) return _random.Next();
чтобы генерировать бесконечный поток случайных чисел.Корректность? Я имею в виду, что перечисляемое ядро может меняться между вызовами. Кэширование приведет к неверным результатам и откроет весь вопрос «когда и как я могу сделать этот кеш недействительным?»
А если учесть , LINQ был первоначально разработан как средство , чтобы сделать LINQ к источникам данных (например , рамки сущности или SQL непосредственно), перечислимое был собирается менять , так как это то, что базы данных делают .
Вдобавок ко всему, есть проблемы Единого принципа ответственности. Гораздо проще создать некоторый код запроса, который работает и построить кеширование поверх него, чем создать код, который запрашивает и кеширует, но затем удаляет кеширование.
источник
ICollection
существует и, вероятно, ведет себя так, как ожидаетIEnumerable
от себяПоскольку LINQ является (и предполагалось с самого начала) универсальной реализацией шаблона Monad, популярного в функциональных языках программирования , и Monad не ограничен тем, чтобы всегда выдавать одни и те же значения при одной и той же последовательности вызовов (фактически, его использование в функциональном программировании популярен именно благодаря этому свойству, которое позволяет избежать детерминированного поведения чистых функций).
источник
Другой причиной, которая не была упомянута, является возможность объединения различных фильтров и преобразований без создания промежуточных результатов.
Возьмите это к примеру:
Если бы методы LINQ вычислили результаты немедленно, у нас было бы 3 коллекции:
Из которых мы заботимся только о последнем. Нет смысла сохранять промежуточные результаты, потому что у нас нет к ним доступа, и мы хотим знать только об автомобилях, которые уже отфильтрованы и сгруппированы по годам.
Если возникла необходимость сохранить какой-либо из этих результатов, решение простое: разбить вызовы на части, вызвать
.ToList()
их и сохранить в переменной.Как примечание, в JavaScript методы Array фактически возвращают результаты немедленно, что может привести к большему потреблению памяти, если не соблюдать осторожность.
источник
По сути, этот код - вставка
Guid.NewGuid ()
внутриSelect
утверждения - очень подозрительно. Это, безусловно, какой-то кодовый запах!Теоретически, мы не обязательно ожидаем, что
Select
оператор создает новые данные, но извлекает существующие данные. Несмотря на то, что для Select целесообразно объединять данные из нескольких источников для создания объединенного контента различной формы или даже для вычисления дополнительных столбцов, мы все же можем ожидать, что он будет функциональным и чистым. ПомещениеNewGuid ()
внутрь делает его не функциональным и не чистым.Создание данных может быть выделено отдельно от выбора и помещено в какую-либо операцию создания, так что выбор может оставаться чистым и повторно используемым, иначе выбор должен быть сделан только один раз и упакован / защищен - это это
.ToList ()
предложение.Однако, чтобы быть ясным, проблема кажется мне смешиванием создания внутри выбора, а не отсутствием кэширования. Помещение
NewGuid()
внутри выбора кажется мне неуместным смешением моделей программирования.источник
Отложенное выполнение позволяет тем, кто пишет код LINQ (точнее, используя
IEnumerable<T>
), явно выбирать, будет ли результат немедленно вычислен и сохранен в памяти или нет. Другими словами, он позволяет программистам выбирать компромисс между временем вычисления и объемом памяти, который наиболее подходит для их применения.Можно утверждать, что большинство приложений сразу же хотят получить результаты, так что это должно было быть поведение по умолчанию LINQ. Но существует множество других API (например
List<T>.ConvertAll
), которые предлагают такое поведение и делают это с момента создания Framework, тогда как до появления LINQ не было никакого способа отложить выполнение. Что, как показали другие ответы, является необходимым условием для включения определенных типов вычислений, которые в противном случае были бы невозможны (исчерпав все доступное хранилище) при использовании немедленного выполнения.источник