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. Это гарантирует, что в моем закрытии не будет ссылок на элементы, которые будут меняться.
До использования местных жителей события будут запускаться с текущими значениями, а не с захваченными значениями, которые я ожидал.