关于c#:访问Modified Closure

Access to Modified Closure

1
2
3
4
5
6
7
8
9
10
11
12
13
string [] files = new string[2];
files[0] ="ThinkFarAhead.Example.Settings.Configuration_Local.xml";
files[1] ="ThinkFarAhead.Example.Settings.Configuration_Global.xml";

//Resharper complains this is an"access to modified closure"
for (int i = 0; i < files.Length; i++ )
{
    // Resharper disable AccessToModifiedClosure
    if(Array.Exists(Assembly.GetExecutingAssembly().GetManifestResourceNames(),
    delegate(string name) { return name.Equals(files[i]); }))
         return Assembly.GetExecutingAssembly().GetManifestResourceStream(files[i]);
    // ReSharper restore AccessToModifiedClosure
}

尽管Resharper抱怨说这是"访问修改后的闭包",但上述方法似乎工作得很好。有人能证明这一点吗?

(本主题在这里继续)


在这种情况下,没关系,因为您实际上是在循环中执行委托。

但是,如果您正在保存代理并稍后使用它,您会发现所有代理在尝试访问文件[i]时都会抛出异常,它们捕获的是变量i,而不是代理创建时的值。

简而言之,这是一个潜在的陷阱,但在这种情况下,它不会伤害你。

请参阅本页底部的更复杂的示例,其中的结果是违反直觉的。


我知道这是一个古老的问题,但我最近一直在研究闭包,并认为代码示例可能有用。在幕后,编译器正在生成一个类,该类表示函数调用的词法闭包。它可能看起来像:

1
2
3
4
5
6
7
8
9
10
private sealed class Closure
{
    public string[] files;
    public int i;

    public bool YourAnonymousMethod(string name)
    {
        return name.Equals(this.files[this.i]);
    }
}

如上所述,您的函数可以工作,因为谓词是在创建后立即调用的。编译器将生成如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private string Works()
{
    var closure = new Closure();

    closure.files = new string[3];
    closure.files[0] ="notfoo";
    closure.files[1] ="bar";
    closure.files[2] ="notbaz";

    var arrayToSearch = new string[] {"foo","bar","baz" };

    //this works, because the predicates are being executed during the loop
    for (closure.i = 0; closure.i < closure.files.Length; closure.i++)
    {
        if (Array.Exists(arrayToSearch, closure.YourAnonymousMethod))
            return closure.files[closure.i];
    }

    return null;
}

另一方面,如果要存储然后稍后调用谓词,您将看到对谓词的每个调用实际上都会在闭包类的同一实例上调用相同的方法,因此将对i使用相同的值。


"files"是一个捕获的外部变量,因为它已被匿名委托函数捕获。匿名委托函数延长了它的生存期。

Captured outer variables
When an outer variable is referenced by an anonymous function, the outer variable is said to have been captured by the anonymous function. Ordinarily, the lifetime of a local variable is limited to execution of the block or statement with which it is associated (Local variables). However, the lifetime of a captured outer variable is extended at least until the delegate or expression tree created from the anonymous function becomes eligible for garbage collection.

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/expressions外部变量

When a local variable or a value parameter is captured by an anonymous function, the local variable or parameter is no longer considered to be a fixed variable (Fixed and moveable variables), but is instead considered to be a moveable variable. Thus any unsafe code that takes the address of a captured outer variable must first use the fixed statement to fix the variable.
Note that unlike an uncaptured variable, a captured local variable can be simultaneously exposed to multiple threads of execution.