访问修改后的封闭

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 抱怨说这是“对修改后的闭包的访问”,但上述方法似乎还是很有效的。有人能解释一下吗?

(本主题继续 给你)

86006 次浏览

在这种情况下,这是可以的,因为你实际上是在执行委托循环。

然而,如果你保存委托并在以后使用它,你会发现所有的委托在试图访问文件[i]时会抛出异常——它们捕获的是变量 i,而不是在委托创建时捕获它的值。

简而言之,这是作为潜在的陷阱要注意的事情,但在这种情况下,它不会伤害你。

请参阅本页底部以获得更复杂的示例,其中结果与直觉相反。

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

private sealed class Closure
{
public string[] files;
public int i;


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

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

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"是捕获的外部变量,因为它已被匿名委托函数捕获。匿名委托函数延长了它的生命周期。

捕获外部变量 当外部变量被匿名函数引用时,外部变量被匿名函数捕获。通常,局部变量的生命周期仅限于与之相关的块或语句的执行(局部变量)。但是,捕获的外部变量的生命周期至少会被延长,直到从匿名函数创建的委托或表达式树符合垃圾收集的条件

MSDN上的外部变量

当局部变量或值参数被匿名函数捕获时,该局部变量或参数不再被认为是固定变量(fixed and moveable variables),而是被认为是可移动变量。因此,任何获取捕获的外部变量地址的不安全代码必须首先使用fixed语句来修复该变量。 注意,与未捕获的变量不同,已捕获的局部变量可以同时暴露给多个执行线程