8

我发现以下内容很奇怪。再说一次,我主要在动态语言中使用闭包,这不应该被同一个“错误”怀疑。以下使编译器不高兴:

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

int i = 1;

它说:

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

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

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

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

4

6 回答 6

9

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

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

于 2009-01-01T12:33:02.430 回答
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 变量持有函数引用,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());

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

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

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

于 2009-01-01T13:26:09.513 回答
1

您还将从如下代码中获得 CS0136:

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

“i”的第二个声明的范围是明确的,像 C++ 这样的语言没有任何问题。但是 C# 语言设计者决定禁止它。鉴于上面的片段,你认为仍然认为这是一个坏主意吗?扔进一堆额外的代码,你可能会盯着这段代码看一会儿,却看不到错误。

解决方法是简单而轻松的,只需想出一个不同的变量名。

于 2009-01-01T13:56:25.680 回答
0

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

int i = 1;
VoidFunction t = delegate { Console.WriteLine(i); };
于 2009-01-01T12:34:59.617 回答
0

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

这是一种解决方法:

class Program
    {
        void Main()
        {
            VoidFunction t = RealFunction;
            int i = 1;
        }
        delegate void VoidFunction();
        void RealFunction() { int i = 0; }
    } 
于 2009-01-01T12:43:52.487 回答
0

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

using System;

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

        int i = 1;
    }
}

...无论您是否在该行中发表评论,您都会得到完全相同的错误。错误帮助显示了一个非常相似的情况。我认为禁止这两种情况是合理的,因为程序员可能会混淆这两个变量。

于 2009-01-01T13:57:27.893 回答