2011-12-27 2 views
10

ReSharper 6.0 дает мне предупреждение «Доступ к модифицированному закрытию» для идентификатора dr в первом фрагменте кода.«Доступ к модифицированному закрытию» разрешен синтаксисом понимания?

private IEnumerable<string> GetTheDataTableStrings(DataTable dt) { 
    foreach (DataRow dr in dt.Rows) { 
     yield return GetStringFuncOutput(() => dr.ToString()); 
    } 
} 

Я думаю, что у меня есть общее понимание того, что это предупреждение пытается защитить меня от: dr меняется несколько раз, прежде чем выход GetTheDataTableStrings опрашивается, и поэтому абонент может не получить выход/поведение я ожидаю.

Но R # не дает мне предупреждения для второго фрагмента кода.

private IEnumerable<string> GetTheDataTableStrings(DataTable dt) { 
    return from DataRow dr in dt.Rows select GetStringFuncOutput(dr.ToString); 
} 

Можно ли отказаться от этого предупреждения/беспокойства при использовании синтаксиса понимания?

Другой код:

string GetStringFuncOutput(Func<string> stringFunc) { 
    return stringFunc(); 
} 
+0

Мне пришлось счистить/упростить этот код перед его представлением. Дайте мне знать, если что-то о коде не позволит вам обсуждать вопрос. – lance

ответ

21

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

Во-вторых, FYI мы, скорее всего, исправим это в следующей версии C#; это серьезная проблема для разработчиков.

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

Цикл «для» не изменяется.

Для получения более подробной информации см. http://ericlippert.com/2009/11/12/closing-over-the-loop-variable-considered-harmful-part-one/.

В-третьих, нет проблем с версией понимания запроса, так как нет модифицированной измененной переменной. Форма запроса понимание такой же, как если бы вы сказали:

return dt.Rows.Select(dr=>GetStringFuncOutput(dr.ToString)); 

лямбда не замкнута на любую внешнюю переменную, так что нет переменной быть изменен случайно.

+0

Прохладный ответ и хорошие новости о исправлениях в C#. Какую версию вы ожидаете получить в C# 5 или 6? –

+1

@ the_joric: Продукт C# 6 не был анонсирован. Мы планируем установить это исправление на C# 5. (Мы должны были переписать код перезаписи замыкания в любом случае, чтобы сделать работу async/await, поэтому возможно, что и это будет исправлено в одно и то же время.) –

+0

Спасибо за обновление Eric :). Это определенно хорошая новость. –

4

Проблема, о которой предупреждает Resharper, была разрешена как на C# 5.0, так и на VB.Net 11.0. Ниже приведены выдержки из языковых спецификаций. Обратите внимание, что спецификации можно найти по следующим путям по умолчанию на машине с установленной Visual Studio 2012.

  • C: \ Program Files (x86) \ Microsoft Visual Studio 11.0 \ VB \ Спецификации \ 1033 \ Visual Basic Язык Specification.docx
  • C: \ Program Files (x86) \ Microsoft Visual Studio 11.0 \ VC# \ Спецификации \ 1033 \ Csharp Язык Specification.docx

C# Спецификация языка Версия 5,0

8,8.4 Еогеасп заявление

Размещение V внутри цикла в то время как важно для того, как она захвачена любой анонимной функции, происходящих во встроенном-заявлении.

Например:

int[] values = { 7, 9, 13 }; 
Action f = null; 
foreach (var value in values) 
{ 
    if (f == null) f =() => Console.WriteLine("First value: " + value); 
} 
f(); 

Если V была объявлена ​​вне цикла в то время, было бы общим среди всех итераций, и его значение после того, как цикл будет конечное значение, 13 , что и выводит вызов f. Вместо этого, поскольку каждая итерация имеет свою собственную переменную v, первая, захваченная f в первой итерации, будет продолжать удерживать значение 7, которое будет напечатано. (Примечание: более ранние версии C# объявил V вне цикла.)

для Microsoft Visual Basic Language Specification Version 11,0

10.9.3 For Each ... Next отчетности (Аннотация)

Существует небольшое изменение в поведении между версиями 10.0 и 11.0 языка. До 11.0 новая итерационная переменная не создавалась для каждой итерации цикла. Эта разница наблюдается только в том случае, если переменная итерации захватывается лямбдой или выражением LINQ, которое затем вызывается после цикла.

Dim lambdas As New List(Of Action) 
For Each x In {1,2,3} 
    lambdas.Add(Sub() Console.WriteLine(x) 
Next 
lambdas(0).Invoke() 
lambdas(1).Invoke() 
lambdas(2).Invoke() 

до Visual Basic 10.0, это произвело предупреждение во время компиляции и печатные "3" в три раза. Это было связано с тем, что все итерации цикла были разделены только одной переменной x, и все три лямбда взяли один и тот же «x», и к тому времени, когда были выполнены лямбды, он удерживал номер 3. As of Visual Basic 11.0, он печатает «1, 2, 3». Это потому, что каждая лямбда захватывает другую переменную «x».

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