2013-02-27 4 views
35

первое заявление:Почему эти линейные выходы различны?

IEnumerable<char> query = "Not what you might expect"; 

query = query.Where (c => c != 'a'); 
query = query.Where (c => c != 'e'); 
query = query.Where (c => c != 'i'); 
query = query.Where (c => c != 'o'); 
query = query.Where (c => c != 'u'); 

Выход String.Join("", query): "Nt wht y mght xpct"

второе утверждение:

query = "Not what you might expect"; 

foreach (char vowel in "aeiou") 
    query = query.Where (c => c != vowel); 

Выход String.Join("", query): "Not what yo might expect"

Результаты этих заявлений различны. Может ли кто-нибудь объяснить, почему?

+4

Какой результат вы получаете? – Default

+1

Результаты этого будут зависеть от того, на какую версию .NET вы нацеливаетесь - какая версия это против? – goric

+1

Действительно ли это, как ваш код структурирован? Значение «vowel» должно быть отменено во втором примере, иначе оно будет просто исполнено как '! = 'U'' 5 раз. –

ответ

57

Если вы используете C# версии ниже 5.0 (где это было зафиксировано), это и есть причина:

лямбда в запросе фиксирует переменную vowel цикла.
Поскольку Linq любит использовать отложенное выполнение, значение этой ссылки не считывается до тех пор, пока запрос не будет выполнен (путем итерации по нему), который после завершения цикла foreach. В этот момент самое последнее значение vowel - u, поэтому вы получаете неожиданный результат.

Вы можете обойти это, скопировав значение на другую временную переменную (или обновив до C# 5.0).

Попробуйте это:

query = "Probably what you might expect"; 

foreach (char vowel in "aeiou") { 
    char currentVowel = vowel; 
    query = query.Where (c => c != currentVowel); 
} 
+0

Просто любопытно, когда C# исправить это? Где я могу прочитать об этом исправлении? Благодарю. –

+3

О, [я вижу] (http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx), в C# 5 –

+2

Удивление, почему это было исправлено в .net 4.5. Поскольку LINQ отложено по исполнению по определению, я не понимаю, зачем изменять прежнее поведение, что на самом деле является более правильным. – Teejay

11

Читайте о закрытии. Если вы используете .NET 4.0 и ниже, вы получите другой результат. В .NET 4.5 это поведение изменяется (фиксировано). См. Также, как компилятор развернуть foreach.

+0

Что касается этой статьи: http://msmvps.com/blogs/kathleen/archive/2012/07/03/lifting-foreach-breaking-change-in-visual-studio-2012.aspx, похоже, это изменение VS 2012, а не на C# 5. На самом деле, я просто попытался с VB на фреймворк 3.5, и он дает новый * ожидаемый * результат. Измените свой ответ, чтобы не путать будущих читателей. – Teejay

+1

@Teejay Это поведение описано в спецификации C# 5.0. Также компиляторы являются частью платформы .NET. –

+1

Возможно. Но вы написали * «Если вы используете .NET 4.0 и ниже, вы получите другой результат». *. Это не правда. Просто попробовал даже на 3.5, и он ведет себя как новый 4.5. – Teejay

13

Это потому, что вы создаете замыкание по переменной vowel, которая изменяется во времени. Сохраните его значение в отдельной переменной, и он будет работать:

query = "Not what you might expect"; 

foreach (char vowel in "aeiou") 
{ 
    var current = vowel; 
    query = query.Where (c => c != current); 
} 
Смежные вопросы