2015-04-11 2 views
1

У меня вопрос о двух битах кода, которые, похоже, работают одинаково с первого взгляда, но дают разные результаты, когда я их создаю и запускаю.Неверное совпадение таблиц LINQ?

код А:

String[] colors = {"green", "red", "blue", "brown"}; 

    var query = colors.Where(c => c.Contains("e")); 

    query = query.Where(c=> c.Contains("n")); 

    Console.WriteLine(query.Count()); 

Это производит консольный вывод «1», так как, предположительно, остались только значение «зеленое» (содержит как е и н).

Если код тонко изменен, однако, результат отличается

Код B:

String[] colors = {"green", "red", "blue", "brown"}; 

    string s = "e"; 
    var query = colors.Where(c => c.Contains(s)); 

    s = "n"; 
    query = query.Where(c=> c.Contains("n")); 

    Console.WriteLine(query.Count()); 

Это производит консольный вывод «2», а также значение, оставшееся в «запросе» является «зеленый, коричневый». Насколько я могу судить, причина в том, что мы присваиваем значение s «n», где ранее было «e». Если я сброшу содержимое «запроса», он будет содержать «зеленый/коричневый», несмотря на то, что я уже рассмотрел запрос ко всем значениям, содержащим «e».

Если бы кто-нибудь мог объяснить, почему это происходит, было бы здорово, спасибо!

ответ

3

Вы видите разные результаты из-за «отсроченного исполнения». То есть когда вы звоните Where(), он фактически не do ничего с вашей коллекцией. Он просто возвращает реализацию IEnumerable<string>, что будет делать, как только вы попытаетесь получить доступ к перечислению.

Итак, в вашем втором примере, где вы ссылаетесь на переменную s в свой запрос лямбда, к моменту выполнения первого запроса вы изменили переменную на значение "n", и поэтому оба запроса выполняют ту же фильтрацию , возвращая полный отсчет 2.

Обратите внимание, что если вы измените пример кода, так что он выглядит следующим образом:

String[] colors = {"green", "red", "blue", "brown"}; 

string s = "e"; 
var query = colors.Where(c => c.Contains(s)).ToArray(); 

s = "n"; 
query = query.Where(c=> c.Contains("n")); 

Console.WriteLine(query.Count()); 

& hellip; он будет производить результат, который вы ожидали. Вышеуказанные вызовы ToArray(), которые заставляют оценивать перечисление, используя текущее значение s в то время. Позже изменения в переменной не повлияют на окончательный результат.

1

Во втором фрагменте вы создаете замыкание вокруг переменной s. Выражение в предложении where не оценивается до тех пор, пока вы не перечислите список с помощью .Count(). В этом случае s уже был переназначен так, что ваш запрос действительно является цветом. Где (c => c.Contains («n»)). Где (c => c.Contains («n»))

1

Причина заключается в том, что запросы LINQ основаны на «отложенном выполнении» - запрос построен, но не выполнен, если вы специально не выполняете повторение по коллекции (например, для цикла foreach) или вызывают методы расширения .ToList() или .ToArray() ,

1

Ваша логика where будет фактически выполнена, когда происходит вызов Count(). Таким образом, до Console.WriteLine ваши лямбда-выражения не выполнялись даже один раз (заставляя вашу строковую переменную оценивать до n в lambdas позже, поскольку присвоение уже выполнено - вы можете проверить это с помощью отладчика). Эта концепция называется Deferred Execution и весьма полезна при запросе баз данных (поскольку SQL скомпилирован и выполняется против базы данных, когда весь запрос известен).

1

Да, вы правы, это из-за s = "n"; Linq оценен ленивым. Здесь в вашем примере первый where не оценивается напрямую. Только когда это необходимо (итерируется). И поскольку там, где лямбды захватили переменную s, текущее значение используется даже для первой лямбда. Попробуйте ToList() перед вторым where, чтобы заставить оценивать первый IEnumerable.

String[] colors = { "green", "red", "blue", "brown" }; 
string s = "e"; 
var query = colors.Where(c => c.Contains(s)).ToList(); 

s = "n"; 
query = query.Where(c => c.Contains("n")); 
Смежные вопросы