委托中变量的范围

我发现以下内容相当奇怪。 然后,我主要使用动态语言中的闭包,这对于同一个“bug”不应该是可疑的。 以下使编译器不满意:

VoidFunction t = delegate { int i = 0; }; int i = 1; 

它说:

名为“i”的局部变量不能在此范围内声明,因为它会为“i”赋予不同的含义,“i”已在“子”范围中用于表示其他内容

所以这基本上意味着在委托中声明的变量将具有声明的函数的范围。不完全是我所期望的。 我甚至没有试过调用这个函数。 至少Common Lisp有一个function,你可以说变量应该有一个动态名称,如果你真的希望它是本地的。 这在创建不泄漏的宏时尤其重要,但这样的东西也会有所帮助。

所以我想知道其他人如何解决这个问题呢?

为了澄清我正在寻找一个解决方案,其中我在delegete中声明的变量不会干扰委托声明的变量。 我仍然希望能够捕获委托之前声明的变量。

它必须允许匿名方法(和lambdas)使用包含方法中的局部变量和参数。

解决方法是为变量使用不同的名称,或者创建一个普通的方法。

匿名函数创建的“闭包”与其他动态语言创建的“闭包”有些不同(我将使用Javascript作为示例)。

 function thing() { var o1 = {n:1} var o2 = {dummy:"Hello"} return function() { return o1.n++; } } var fn = thing(); alert(fn()); alert(fn()); 

这个小块的javascript将显示1然后2.匿名函数可以访问o1变量,因为它存在于其作用域链上。 但是,匿名函数具有完全独立的范围,在该范围内,它可以创建另一个o1变量,从而隐藏范围链中的任何其他变量。 还要注意,整个链中的所有变量都保留,因此只要fn varialbe保存函数引用,o2就会继续存在,并保持对象引用。

现在与C#匿名函数进行比较: –

 class C1 { public int n {get; set;} } class C2 { public string dummy { get; set; } } Func thing() { var o1 = new C1() {n=1}; var o2 = new C2() {dummy="Hello"}; return delegate { return o1.n++; }; } ... Func fn = thing(); Console.WriteLine(fn()); Console.WriteLine(fn()); 

在这种情况下,匿名函数不会创建一个真正独立的范围,而不是任何其他函数内{}代码块中的变量声明(在foreachif等)

因此适用相同的规则,块外的代码不能访问块内声明的变量,但也不能重用标识符。

当匿名函数在创建它的函数之外传递时,会创建一个闭包。来自Javascript示例的变化是只有匿名函数实际使用的那些变量才会保留,因此在这种情况下,o2持有的对象将一旦完成,就可用于GC,

你也可以从这样的代码得到CS0136:

  int i = 0; if (i == 0) { int i = 1; } 

“i”的第二个声明的范围是明确的,像C ++这样的语言没有任何优点。 但C#语言设计师决定禁止它。 鉴于上述片段,您认为仍然认为这是一个坏主意吗? 抛出一堆额外的代码,你可以盯着这段代码一段时间而不是看到bug。

解决方法是微不足道和无痛的,只是提出了一个不同的变量名称。

这是因为委托可以引用委托之外的变量:

 int i = 1; VoidFunction t = delegate { Console.WriteLine(i); }; 

如果我没记错的话,编译器会创建匿名方法中引用的外部变量的类成员,以使其工作。

这是一个解决方法:

 class Program { void Main() { VoidFunction t = RealFunction; int i = 1; } delegate void VoidFunction(); void RealFunction() { int i = 0; } } 

实际上,错误似乎与匿名委托或lamda表达式没有任何关系。 如果您尝试编译以下程序…

 using System; class Program { static void Main() { // Action t = delegate { int i = 0; }; int i = 1; } } 

…无论你是否在线上评论,你都会得到完全相同的错误。 错误帮助显示了一个非常类似的情况。 我认为不允许这两种情况是合理的,因为程序员可能会混淆这两个变量。