【问题标题】:Scope of variables in a delegate委托中的变量范围
【发布时间】:2009-01-01 12:23:25
【问题描述】:

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

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

int i = 1;

上面写着:

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

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

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

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

【问题讨论】:

    标签: c# functional-programming delegates lambda scope


    【解决方案1】:

    必须以这种方式允许匿名方法(和 lambdas)使用局部变量和范围在包含方法中的参数。

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

    【讨论】:

    • 但是变量 i 是在委托 之后声明的。编译器告诉我不能在该范围内声明具有相同名称的变量,这真的没有多大帮助。我什至可能不会在该范围内调用该函数。
    • 委托没有独立作用域,就像 if 或 foreach 代码块一样。详情见我的回答。
    • 在委托之前或之后声明变量 i 无关紧要。当编译成 IL 时,变量 i 成为局部变量,在函数调用时分配在堆栈上。它不会在函数中间“开始存在”。 (当然,如果委托或 lambda 表达式使用 i,那么它会被编译为类字段而不是局部变量,但这是一个单独的主题。)
    【解决方案2】:

    匿名函数创建的“闭包”与其他动态语言创建的“闭包”有些不同(我将以 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());
    

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

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

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

    【讨论】:

    • 谢谢,但他们为什么要创建如此可怕的范围规则?在哪种情况下,作用域局部变量会阻止创建同名的外部变量?最多应该是一个警告?
    • 这是一个很好的问题,我不确定我是否知道答案。它可能与性能和编译器优化的能力有关。或许你可以在 SO 中专门问一下。
    • 好主意,在这里问:stackoverflow.com/questions/405116/…
    【解决方案3】:

    您还将通过以下代码获得 CS0136:

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

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

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

    【讨论】:

    • 这就是为什么我们有警告不是吗?
    • Hmya,警告...你也喜欢 C/C++ 编译器的 /Wx 选项吗?一个说“不”而不是“也许”的语言设计师得到了我的投票。
    • 在这种情况下是的。因为我认为委托案例是一种语言错误,因为它使我不得不编写次优代码以使编译器满意。该语言应该与您合作,而不是对您不利...
    • 为了清楚起见,我认为您的示例可能应该产生编译错误,而对于问题中发布的示例,我仍然坚持我的最后评论:)
    • 嗯,没关系。但是你和一块硅发生了争执,它既不听也不回话。发布到 connect.microsoft.com
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

      这是一个解决方法:

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

      【讨论】:

        【解决方案6】:

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-28
          • 1970-01-01
          • 1970-01-01
          • 2020-03-05
          • 2015-11-03
          • 1970-01-01
          相关资源
          最近更新 更多