【问题标题】:Access to modified closure... but why?访问修改后的闭包...但是为什么呢?
【发布时间】:2011-04-29 07:17:45
【问题描述】:

在这里看到几个类似的问题,但似乎都不是我的问题......

我了解(或认为我了解)闭包的概念,并且了解导致 Resharper 抱怨访问修改后的闭包的原因,但在下面的代码中,我不明白我是如何违反闭包的。

因为primaryApps 是在for 循环的上下文中声明的,所以primary 在我处理primaryApps 时不会改变。如果我在 for 循环之外声明了primaryApps,那么我绝对有关闭问题。但是为什么在下面的代码中呢?

var primaries = (from row in openRequestsDataSet.AppPrimaries
                 select row.User).Distinct();

    foreach (string primary in primaries) {

        // Complains because 'primary' is accessing a modified closure
        var primaryApps = openRequestsDataSet.AppPrimaries.Select(x => x.User == primary);

Resharper 是不是不够聪明,无法确定这不是问题,还是我没有看到这里关闭是一个问题的原因?

【问题讨论】:

  • 您确定要使用“选择”而不是“位置”吗?
  • Select 返回一个类型化数据行的数组...其中返回一个 IEnumerable。任何一个都适合我正在做的事情;我读过文章说 .Select 在我使用的条件下更快。也许是,也许不是,很难说。
  • 我收回了...因为我有一个 lambda,我没有使用内置的 datatable.select(),所以我没有得到一个类型化行的数组。在这种情况下,你是对的,我没有转换值,所以 where 更有意义。
  • 警告是关于一个已经导致很多人陷入很多问题的陷阱 - 只需查看关于闭包和 foreach 循环的 SO 问题的数量。因此,我认为这是一个很好的警告。
  • 我认为这是一个很好的警告,我不会忽视。只需要了解为什么我会在这种情况下得到它,我现在就这样做:)

标签: c# linq resharper closures


【解决方案1】:

问题出在下面的语句中

因为 primaryApps 是在 for 循环的上下文中声明的,所以在我处理 primaryApps 时,primary 不会改变。

Resharper 根本无法 100% 验证这一点。此处引用闭包的 lambda 被传递给此循环上下文之外的函数:AppPrimaries.Select 方法。该函数本身可以存储生成的委托表达式,稍后执行它并直接运行到迭代变量问题的捕获中。

正确检测这是否可能是一项艰巨的任务,坦率地说不值得付出努力。取而代之的是,ReSharper 采取了安全路线,并就迭代变量的潜在危险捕获发出警告。

【讨论】:

  • 此外,Visual Basic 的编译器会在完全相同的情况下发出警告。 C# 团队曾考虑采用 VB/Resharper 警告,但将决定推迟到下一个版本。
  • @Jonathan无耻插件,这里有一篇文章深入解释VB.Net消息blogs.msdn.com/b/jaredpar/archive/2007/07/26/…
  • 所以从你和 Eric 的说法来看,问题是 AppPrimaries.Select() 是静态的,可以将 lambda 存储在静态变量中,而另一个调用,例如,AppPrimaries.CalculateSelectLambda() 将导致延迟到原始循环长时间完成的执行。此时,primary 将保持上次循环中设置的任何值。
  • @James 正确。尽管如果 Select 是一个实例还是一个扩展方法,这同样适用。
【解决方案2】:

因为 primaryApps 是在 for 循环的上下文中声明的,所以在我处理 primaryApps 时,primary 不会改变。如果我在 for 循环之外声明了 primaryApps,那么绝对会遇到关闭问题。但是为什么在下面的代码中呢?

贾里德是对的;为了说明为什么你的结论不符合你的前提,让我们编写一个程序,在 for 循环的上下文中声明 primaryApps, 并且 仍然遇到捕获的循环变量问题。很容易做到这一点。

static class Extensions
{
    public IEnumerable<int> Select(this IEnumerable<int> items, Func<int, bool> selector)
    {
        C.list.Add(selector);
        return System.Enumerable.Select(items, selector);
    }
}

class C
{
    public static List<Func<int, bool>> list = new List<Func<int, bool>>();
    public static void M()
    { 
        int[] primaries = { 10, 20, 30}; 
        int[] secondaries = { 11, 21, 30}; 

        foreach (int primary in primaries) 
        {
            var primaryApps = secondaries.Select(x => x == primary);
            // do something with primaryApps
        }
        C.N();
    }
    public static void N()
    {
        Console.WriteLine(C.list[0](10)); // true or false?
    }
}

声明“primaryApps”的地方完全不相关。唯一相关的是闭包可能会在循环中存活,因此有人可能会稍后调用它,错误地认为闭包中捕获的变量是按值捕获的.

Resharper 无法知道 Select 的特定实现不会隐藏选择器以供以后使用;事实上,这正是他们所有所做的。 Resharper 怎么会知道他们碰巧把它藏在了以后无法访问的地方?

【讨论】:

  • 我认为您的意思是“Resharper”,而不是“Reflector”;)
  • 我要强调的是,警告与 primary 在循环外声明有关,而不是 primaryApps 在循环内声明。
【解决方案3】:

据我所知,Resharper 每次访问 foreach 变量时都会生成警告,即使它并没有真正导致关闭。

【讨论】:

  • 仅当您在 lambda 或其他延迟执行上下文中使用变量时
【解决方案4】:

是的,这只是警告, 看 : http://devnet.jetbrains.net/thread/273042

【讨论】:

  • 是的,我知道这只是一个警告......只需要知道我是否可以安全地忽略它:) 答案显然是“不”
猜你喜欢
  • 2017-04-24
  • 2017-01-26
  • 2011-09-29
  • 2011-07-28
  • 2010-09-23
  • 2015-11-08
  • 1970-01-01
  • 2012-09-14
相关资源
最近更新 更多