ReSharper Warning - Доступ к измененному закрытию

У меня есть следующий код:

string acctStatus = account.AccountStatus.ToString();
if (!SettableStatuses().Any(status => status == acctStatus))
    acctStatus = ACCOUNTSTATUS.Pending.ToString();

Обратите внимание, что account.AccountStatus - это перечисление типа ACCOUNTSTATUS. Во второй строке ReSharper выдает предупреждение "Доступ к измененному закрытию" для acctStatus. Когда я выполняю рекомендованную операцию " Копировать в локальную переменную", она модифицирует код следующим образом:

string acctStatus = realAccount.AccountStatus.ToString();
string s = acctStatus;
if (!SettableStatuses().Any(status => status == s))
    acctStatus = ACCOUNTSTATUS.Pending.ToString();

Почему это лучше или предпочтительнее того, что я имел изначально?

РЕДАКТИРОВАТЬ

Он также рекомендует Wrap локальной переменной в массиве, который производит:

string[] acctStatus = {realAccount.AccountStatus.ToString()};
if (!SettableStatuses().Any(status => status == acctStatus[0]))
    acctStatus[0] = ACCOUNTSTATUS.Pending.ToString();

Это кажется совершенно дурацким для меня.

1 ответ

Решение

Причина предупреждения заключается в том, что внутри цикла вы можете обращаться к переменной, которая меняется. Однако "исправление" ничего не делает для вас в этом неконтурном контексте.

Представьте, что у вас есть цикл FOR и внутри него if, а за пределами объявления строки. В этом случае ошибка будет правильно определять проблему захвата ссылки на что-то нестабильное.

Пример того, что вы не хотите:

string acctStatus

foreach(...)
{
  acctStatus = account.AccountStatus[...].ToString();
  if (!SettableStatuses().Any(status => status == acctStatus))
      acctStatus = ACCOUNTSTATUS.Pending.ToString();
}

Проблема в том, что замыкание захватит ссылку на acctStatus, но каждая итерация цикла будет изменять это значение. В таком случае было бы лучше:

foreach(...)
{
  string acctStatus = account.AccountStatus[...].ToString();
  if (!SettableStatuses().Any(status => status == acctStatus))
      acctStatus = ACCOUNTSTATUS.Pending.ToString();
}

Поскольку контекст переменной является циклом, каждый раз будет создаваться новый экземпляр, потому что мы переместили переменную в локальный контекст (цикл for).

Рекомендация звучит как ошибка в разборе Resharper этого кода. Однако во многих случаях это действительная проблема (например, в первом примере, когда ссылка меняется, несмотря на ее захват в закрытии).

Мое эмпирическое правило таково, когда сомневаешься сделать местным.

Вот реальный пример, который меня укусил:

        menu.MenuItems.Clear();
        HistoryItem[] crumbs = policyTree.Crumbs.GetCrumbs(nodeType);

        for (int i = crumbs.Length - 1; i > -1; i--) //Run through items backwards.
        {
            HistoryItem crumb = crumbs[i];
            NodeType type = nodeType; //Local to capture type.
            MenuItem menuItem = new MenuItem(crumb.MenuText);
            menuItem.Click += (s, e) => NavigateToRecord(crumb.ItemGuid, type);
            menu.MenuItems.Add(menuItem);
        }

Обратите внимание, что я фиксирую тип NodeType local, note nodeType и HistoryItem crumb.ItemGuid, а не crumbs [i].ItemGuid. Это гарантирует, что в моем закрытии не будет ссылок на элементы, которые будут меняться.

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

Другие вопросы по тегам