ReSharper警告 - 访问修改的闭包

38

我有以下代码:

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

为什么这种方式比我最初使用的更好或更可取?

编辑

它还建议将本地变量包装在数组中,具体方法如下:

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

这对我来说似乎非常荒谬。


请查看这个SO问题和被接受的答案,可能会有所帮助。https://dev59.com/5XVC5IYBdhLWcg3woCjN - user1921
1个回答

35

警告的原因是在循环内部可能会访问正在变化的变量。然而,在这种非循环上下文中,“修复”实际上对你没有任何作用。

想象一下,如果你有一个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类型的本地变量nodeType和HistoryItem导航项crumb.ItemGuid,而不是crumbs[i].ItemGuid。这可以确保我的闭包不会引用将要更改的项。
在使用本地变量之前,事件会触发当前值,而不是我预期的捕获值。

1
问题在于闭包会抓取对acctStatus的引用,但每次循环迭代都会更改该值 - 实际上,每当“Any”谓词评估它时(因为“Any”在返回之前遍历可枚举对象),它将始终是account.AccountStatus[...].ToString(),所以我不确定这是否是一个好的例子。我相信您提出的更好的方法将具有相同的行为。 - Ohad Schneider
1
这是不正确的。差异的原因在于第一个示例中只分配了一个变量:acctStatus。Resharper担心在实际评估Any()之前,该变量将发生更改。我的建议根本不改变代码的操作(Any()在发生任何更改之前被评估),但明确表示我们希望为循环的每次迭代都有一个不同的acctStatus变量实例。如果您注意到我稍后的示例,Resharper担心的错误实际上会触发,因为我没有立即评估lambda(减去locals)。 - Godeke

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接