2010-05-13 2 views
5

Когда я запускаю этот фрагмент кода:Почему мои связанные параметры идентичны (используя Linq)?

string[] words = new string[] { "foo", "bar" }; 
var results = from row in Assets select row; 
foreach (string word in words) 
{ 
    results = results.Where(row => row.Name.Contains(word)); 
} 

Я получаю SQL:

-- Region Parameters 
DECLARE @p0 VarChar(5) = '%bar%' 
DECLARE @p1 VarChar(5) = '%bar%' 
-- EndRegion 
SELECT ... FROM [Assets] AS [t0] 
WHERE ([t0].[Name] LIKE @p0) AND ([t0].[Name] LIKE @p1) 

Обратите внимание, что @p0 и @p1 оба bar, когда я хотел, чтобы они foo и bar.

Я полагаю, что Linq каким-то образом связывает ссылку на переменную word, а не ссылку на строку, на которую ссылаются в настоящее время word? Каков наилучший способ избежать этой проблемы?

(Кроме того, если у вас есть какие-либо предложения для лучшего названия для этого вопроса, пожалуйста, поместите его в комментариях.)

Обратите внимание, что я попробовал это с регулярным Linq также, с теми же результатами (вы можете вставить это право в LINQPad):

string[] words = new string[] { "f", "a" }; 
string[] dictionary = new string[] { "foo", "bar", "jack", "splat" }; 
var results = from row in dictionary select row; 
foreach (string word in words) 
{ 
    results = results.Where(row => row.Contains(word)); 
} 
results.Dump(); 

Сплин:

bar 
jack 
splat 
+1

Дополнительные комментарии и анализ этого вопроса см http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered- harm.aspx и http://blogs.msdn.com/ericlippert/archive/2009/11/16/closing-over-the-loop-variable-part-two.aspx –

ответ

8

вы используете то, что называется «закрытие», что означает, что вы определяете анонимную функцию (ваш лямбда) который использует локальную переменную в своем теле. В частности, вы «закрываете» по переменной цикла word. Проблема, возникающая с закрытием, возникает из с задержкой выполнения, что означает, что тело вашей лямбды не запускается, когда вы ее определяете, но когда оно вызывается.

Из-за этого вы почти никогда не хотите закрывать переменную цикла. Из-за задержки выполнения значение переменной в лямбда будет равно при выполнении лямбда. Для lambdas, объявленного внутри цикла и вызываемого вне него, это означает, что он всегда будет иметь последнее значение из цикла.

Чтобы противодействовать этому, используйте локальную переменную, объявленную внутри цикла. Это заставит его зафиксировать значение в этот момент времени и передать новую переменную на каждую созданную лямбду. Как так:

string[] words = new string[] { "foo", "bar" }; 
var results = from row in Assets select row; 
foreach (string word in words) 
{ 
    string tempWord = word; 

    results = results.Where(row => row.Name.Contains(tempWord)); 
} 
+0

Спасибо, отличное описание и даже работает! :) –

Смежные вопросы