JetBrains Rider 2025.2 Help

代码检查:访问已修改的捕获变量

首先,让我们确保您理解什么是 闭包。 简单来说,C# 中的闭包是一个 lambda 表达式或匿名方法,它捕获了外部作用域中的一些变量。 以下是最简单的示例:

// A self-contained lambda. Not a closure. Action printOne = () => { Console.WriteLine("one"); }; // A closure – a lambda that captures a variable from an outer scope. string myStr = "one"; Action print = () => { Console.WriteLine(myStr); };

在上述示例中, 打印 将捕获变量 myStr (而不是其 ),并且仅在您调用 打印() 时获取 myStr

在更复杂的场景中,当闭包定义在一个变化的上下文中时,它可能不会按预期运行。

以下是在循环中定义上述闭包的示例:

var myActions = new List<Action>(); var myStrings = new List<string>() { "one", "two", "three" }; for (var i = 0; i < myStrings.Count; i++) { Action print = () => { Console.WriteLine(myStrings[i]); }; myActions.Add(print); } myActions[0]();

令人惊讶的是,当我们调用 myActions[0](); 时,此代码会生成一个 ArgumentOutOfRangeException。 这里发生的情况是:此调用尝试执行 Console.WriteLine(myStrings[i]); ,而不是执行 Console.WriteLine(myStrings[0]); (尽管这可能看起来更直观),并且由于 i 的作用域是整个 for 循环,其值不等于 0 ,甚至不等于 2 (这是条件最后一次为 true 时的值)。 作为最后一次 ++ 操作的结果,在条件变为 false 并退出循环之前,值变为 3。 由于 myStrings 只有 3 个元素, myStrings[3] 导致 ArgumentOutOfRangeException

尽管 JetBrains Rider 未推断出后果(在这里表现为 ArgumentOutOfRangeException 的形式),但它正确地指向了问题的根源——闭包中的迭代变量——并建议通过将变化变量的值复制到定义闭包的作用域中来修复它:

for (var i = 0; i < myStrings.Count; i++) { var i1 = i; Action print = () => { Console.WriteLine(myStrings[i1]); }; myActions.Add(print); }

此修复确保当您从 myActions 中选择一个操作并获取创建此操作的上下文时, i1 将持有与列表中该操作索引对应的值。

最后修改日期: 2025年 9月 26日