«Доступ к измененному закрытию» разрешается синтаксисом понимания? - PullRequest
10 голосов
/ 28 декабря 2011

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();
}

Ответы [ 2 ]

22 голосов
/ 28 декабря 2011

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

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

(ОБНОВЛЕНИЕ: Этот ответ был написан в 2011 году. На самом деле мы приняли исправление, описанное ниже в C # 5.)

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

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

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

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

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

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

5 голосов
/ 18 сентября 2012

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

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

C # Language Specification Version 5.0

8.8.4 Оператор foreach

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

Например:

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

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

Спецификация языка Microsoft Visual Basic Версия 11.0

10.9.3 For Each ... Next Statements (Аннотация)

В версии 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. Начиная с Visual Basic11,0, он печатает «1, 2, 3».Это потому, что каждая лямбда захватывает свою переменную "x".

...