Хороший вопрос. Как указывает Рид, все они в основном проистекают из отложенного выполнения (но в отличие от него я считаю это недостатком. Просто думать, почему нельзя выполнять отложенные исполнения, запоминая состояние). Вот пара примеров - все это более или менее варианты проблемы отложенного выполнения.
1) Мне лень что-то делать вовремя
Linq выполняется только по запросу.
Распространенная ошибка, которую допускают новички (включая меня в прошлом), - это отсутствие знания об отложенном исполнении Например, что-то вроде
var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty);
выполняется в один миг, но фактическая сортировка не завершена, пока вы не перечислите список, иными словами, выполнение не будет завершено, пока вам не понадобится результат выполнения. Чтобы выполнить его, вам нужно что-то вроде:
foreach(var item in p)...
//or
p.Count();
//or
p.ToList();
//etc
См. Их как запросы SQL. Если у вас есть
var query = from i in otherValues where i > 5 select i;
думаю, что это сродни написанию
string query = "SELECT i FROM otherValues WHERE i > 5";
Последний запускает вызов БД? Вы должны
Execute(query);
Здесь то же самое и с Linq.
2) Я живу в настоящем
Будьте осторожны с изменением переменных внутри выражений Linq
позже.
В целях безопасности сначала создайте резервную копию переменных, а затем используйте резервную копию в запросе, если переменная может измениться позднее до фактического выполнения запроса.
Отсюда:
decimal minimumBalance = 500;
var customersOver500 = from c in customers
where c.Balance > minimumBalance
select c;
minimumBalance = 200;
var customersOver200 = from c in customers
where c.Balance > minimumBalance
select c;
int count1 = customersOver500.Count();
int count2 = customersOver200.Count();
Предположим, у нас есть четыре клиента со следующими балансами: 100, 300, 400 и 600. Какими будут count1 и count2? Они оба будут равны 3. «CustomersOver500» ссылается на переменную «imumBalance », но значение не будет получено до тех пор, пока результаты запроса не будут перебраны (через цикл for / each, вызов ToList () или даже« Count » () "вызов, как показано выше). В то время, когда значение используется для обработки запроса, значение минимального баланса уже изменилось на 200, поэтому оба запроса LINQ дают одинаковые результаты (клиенты с балансом более 200).
3) Моя память слишком слаба, чтобы вспомнить ценности прошлого
То же, что и выше, контекст немного отличается.
или это с того же сайта:
Рассмотрим простой пример метода, использующего LINQ-to-SQL для получения списка клиентов:
public IEnumerable<Customer> GetCustomers()
{
using(var context = new DBContext())
{
return from c in context.Customers
where c.Balance > 2000
select c;
}
}
Кажется довольно безобидным - пока вы не получите «ObjectDisposedException», когда вы пытаетесь перечислить коллекцию. Зачем? Поскольку LINQ фактически не выполняет запрос, пока вы не попытаетесь перечислить результаты. Класс DBContext (который предоставляет коллекцию Customers) удаляется при выходе из этого вызова. После того, как вы попытаетесь выполнить перечисление в коллекции, на класс DBContext.Customers будет дана ссылка, и вы получите исключение.
4) Не пытайтесь меня поймать, я все равно могу ускользнуть
Try-catch бессмысленно утверждать, если не использовать его разумно.
Вместо этого будет лучше обрабатывать глобальные исключения.
try
{
wallet = bank.Select(c => Convert.ToInt32(""));
}
catch (Exception ex)
{
MessageBox.Show("Cannot convert bad int");
return;
}
foreach(int i in wallet)
//kaboom!
Ни мы не получаем правильное сообщение об ошибке, ни функция не завершаются return
.
5) Я не только не пунктуален, но и не учусь на ошибках
Linq выполняется каждый раз, когда вы перечисляете их. Поэтому не используйте повторно перечислимые числа в Linq.
Предположим, у вас есть IQueryable
или IEnumerable
, возвращенные из выражения Linq. Теперь при перечислении коллекции будет выполнен оператор, но только один раз? Нет, каждый раз, когда ты это делаешь. Это укусило меня в прошлом. Если у вас есть:
var p = listOfAMillionComplexItems.OrderBy(x => x.ComplexProperty);
MessageBox.Show(p.Count().ToString()); //long process.
MessageBox.Show(p.Count().ToString()); //long process still.
Так лучше сделайте
int i = p.Count(); //store in a variable to access count
//or better
var list = p.ToList(); //and start using list
6) Если вы не знаете, использовать меня, я могу вызвать побочные эффекты!
То же, что и выше, просто чтобы показать, как повторное использование перечислимых Linq может вызвать нежелательное поведение.
Убедитесь, что вы не программируете побочные эффекты (поскольку повторное перечисление в Linq встречается гораздо чаще). Чтобы привести пример,
p = bag.Select((t, i) => {if(i == 1) MessageBox.Show("Started off"); return t;});
Если вы дважды перечислите, вы знаете, что может произойти нежелательно.
7) Будьте осторожны с порядком, который я выполняю при цепочке
Не только для переменных, даже связанные функции Linq могут выполняться в порядке, отличном от того, что вы обычно ожидаете (хотя поведение корректно). Не думайте об императиве (шаг за шагом), подумайте, как Linq может выполнить его.
Например,
var d = Enumerable.Range(1, 100);
var f = d.Select(t => new Person());
f = f.Concat(f);
f.Distinct().Count() ??
Каким будет количество отдельных людей в f
? Я бы предположил 100, нет, но это 200. Проблема в том, что когда происходит фактическое выполнение логики конкатенации, f
все еще d.Select(t => new Person()
неисполнено . Так что это эффективно дает в
f = d.Select(t => new Person()).Concat(d.Select(t => new Person()));
, который тогда имеет 200 различных членов. Вот ссылка на актуальную проблему
8) Эй, на самом деле мы умнее, чем вы думаете.
Не предостережение само по себе, но во многих случаях Linq может превзойти вашу программу императивного стиля. Поэтому, прежде чем приступать к оптимизации, подумайте и даже сравните результаты.
Причина, по которой отложенное выполнение в основном выполняется по требованию, делает Linq гораздо более эффективным, чем кажется. Блок итератора «выдает» по одному элементу за раз, как требуется, предоставляя возможность останавливать выполнение, когда он больше не нужен. Вот очень хороший вопрос, который описывает только это: Порядок методов расширения LINQ не влияет на производительность?
9) Я не собираюсь хрустить число
Злоупотребление Linq может сделать код неэффективным, а также менее читабельным.
Для алгоритмов сокращения чисел Linq не является подходящим инструментом, особенно для больших наборов данных, сложность которых может увеличиваться в геометрической прогрессии. Иногда лучше всего две петли для. То же самое можно применить к сырому SQL по сравнению с LINQ to SQL.
10) Наймите меня на нужную работу
Спрашивать Линка о том, что ваш обычный бизнес - плохой выбор программирования, что противоречит удобочитаемости.
Некоторые, например:
medicines.Any(p =>
{
Console.WriteLine(p);
return false;
});
для foreach на перечисляемом.
или
medicines = medicines.Select(p =>
{
p.Id = 3;
return p;
});
Просто плохие инструменты.
11) Отладка и профилирование могут быть кошмаром
Трудно следить за тем, что происходит под капотом, с выражением Linq из VS
Не то чтобы это совершенно невозможно, но задача отладки linq-запроса столь же эффективна, как и не linq-код от самой VS. Профилирование также становится немного сложнее из-за характера отложенного выполнения. Но это не должно мешать кому-либо делать тривиальный вкладыш!
Куча предостережений, более или менее связанных с отложенным исполнением! Здесь такой же вопрос . Некоторые связанные чтения на SO:
Примеры того, когда не следует использовать LINQ
Плюсы и минусы LINQ (Language-Integrated Query)
Какую самую большую ошибку делают люди, когда начинают использовать LINQ?
недостатки linq