隐式捕获闭包,ReSharper警告

32

我通常知道"隐式捕获闭包"是什么意思,但今天我遇到了以下情况:

public static void Foo (Bar bar, Action<int> a, Action<int> b, int c)
{
    bar.RegisterHandler(x => a(c)); // Implicitly captured closure: b
    bar.RegisterHandler(x => b(c)); // Implicitly captured closure: a
}

为什么我会隐式地捕获其他操作?如果我注释其中一行,另一行就不会给我警告。有人知道ReSharper在警告我什么危险吗?

编辑:ReSharper 8.0.1


1
不,这不是13633617的重复,它特别涉及到未使用的变量被第二个lambda隐式捕获的情况。 - D.R.
你应该阅读我在提到的问题上的回答,我认为那正是你所问的。这里 - quadroid
3个回答

34
问题在于当你闭合一个变量时,编译器在幕后创建一个新的未命名类型,并为代码块中闭合的每个变量添加实例字段,为代码块中的每个匿名方法添加一个方法,然后传递该对象的单个实例。
这意味着第一个委托的生命周期使得该闭包对象保持活动状态,并且它内部引用了对象b,除了a以外,反之亦然。
在您的情况下,这不是问题,因为Action并不是特别占用内存,因此使其活动一段时间并不是真正的问题。
在理论上,C#团队可以确保在同一块中针对每个闭包创建一个新的未命名类型,但他们选择不这样做,因为这会使常见情况更糟。

谢谢,我知道了。除了将这两行移动到不同的类型之外,还有别的办法吗? - D.R.
2
@D.R. 嗯,就像我说的,在这种情况下,你可以不用太在意。如果它们是昂贵的对象,并且能够更早地清除它们的内存很重要,那么你可以不使用闭包来实现。自己创建新的命名类型,带有每个类型中每个操作的方法和实例字段,在此方法中创建它们的实例等。闭包实际上并没有让你做任何你无法做到的事情,它们只是消除了样板代码,并使其更容易实现。 - Servy
您可以使用ReSharper的注释功能,向RegisterHandler方法添加[InstantHandle]属性。这告诉ReSharper lambda表达式会立即执行,因此闭包的范围是有限的,ReSharper不会显示警告。 - citizenmatt
1
根据所显示的名称和上下文,它们似乎并不会立即执行。我猜测它会保留委托直到稍后,可能是相当长的时间。问题在于被闭合的对象不太可能很昂贵,并且延长其生命周期也不会受到很大影响。这是可能的,但不太可能发生。重要的是,操作及其中引用的任何对象的寿命不应比 bar 短或者不特别占用内存。 - Servy
1
好的观点。应该把这一点说明清楚。当然,如果是这种情况,那么警告是有用的,并且应该促使检查代码,看看在这里是否适合延长生命周期。如果适合,可以安全地忽略警告,或者使用正确格式的注释在本地禁用它 - 使用警告上的alt+enter将帮助生成这些注释。 - citizenmatt

0

顺便提醒一下,我曾经因这个问题而抓狂:

List<List<string>> allowed = AllowedSCACSwaps;
foreach (List<string> c in allowed.Where(c => c.Contains(scac)))
{
    csc = openCycles.FirstOrDefault(icsc => (icsc.CustomerCode == customerCode) && c.Contains(icsc.SCAC));
    if (null != csc)
    {
        return csc;
    }
}

说“customerCode的隐式闭包”

string cc = customerCode.ToUpperInvariant();
string sc = scac.ToUpperInvariant();    List<List<string>> allowed = AllowedSCACSwaps;
    foreach (List<string> c in allowed.Where(c => c.Contains(sc)))
    {
        csc = openCycles.FirstOrDefault(icsc => (icsc.CustomerCode == cc) && c.Contains(icsc.SCAC));
        if (null != csc)
        {
            return csc;
        }
    }

没问题。

我为什么要发疯呢?

scac和customerCode都是传递到方法中的字符串。 但即使我用cc替换了customerCode,我仍然收到相同的警告。

闭包实际上是在scac上,但Resharper错误地报告了它。


哎呀,代码不支持加粗啊 :(. 警告信息在这里出现: allowed.Where(c => c.Contains(scac)) - Roger Willcocks
这不是误报。每个闭包的警告列出了在该闭包中实际上未使用但仍被捕获的变量。因此,在原始代码中,您应该收到两个警告:一个关于Contains(scac)customerCode的警告,以及一个关于icsc.CustomerCode == customerCodescac的警告。 - Miral
除了闭包警告是在第3行,而不是第5行。这对我来说没有意义,除非你是在说foreach的主体被转换为匿名方法调用。 - Roger Willcocks
仅查看顶部代码片段,您应该在第2行的“customerCode”和第4行的“scac”处收到警告,因为这是相关的lambda出现的位置。 - Miral

0

我以前见过这种情况;它与变量在 lambda 的生命周期内被保留有关,因此如果它们很大,就会产生内存压力。


找到了原始对话的链接 http://devnet.jetbrains.com/thread/436232 - Allan Elder
1
是的,但有趣的是第一个 lambda 为什么持有对 b 的引用,而第二个 lambda 则持有对 a 的引用。 - Servy

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