委托中变量的范围

时间:2009-01-01 12:23:26

标签: c# functional-programming delegates lambda scope

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

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

int i = 1;

它说:

  

名为“i”的局部变量不能   在此范围内声明,因为它   会给'我'一个不同的含义,   已经在“孩子”中使用   范围来表示别的东西

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

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

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

6 个答案:

答案 0 :(得分:9)

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

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

答案 1 :(得分:3)

匿名函数创建的“闭包”与其他动态语言创建的“闭包”略有不同(我将使用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<int> thing() {
   var o1 = new C1() {n=1};
   var o2 = new C2() {dummy="Hello"};
   return delegate { return o1.n++; };
}
...
Func<int> fn = thing();
Console.WriteLine(fn());
Console.WriteLine(fn());

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

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

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

答案 2 :(得分:1)

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

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

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

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

答案 3 :(得分:0)

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

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

答案 4 :(得分:0)

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

这是一种解决方法:

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

答案 5 :(得分:0)

实际上,错误似乎与匿名委托或lamda表达式无关。如果您尝试编译以下程序......

using System;

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

        int i = 1;
    }
}

...无论你是否评论,你都会得到完全相同的错误。 error help显示了一个非常相似的案例。我认为不允许这两种情况是合理的,理由是程序员可能会混淆这两个变量。