18
    var x = 1;
    Func<int,int> f = y => x + y;
    x = 2;
    Console.WriteLine(f(1));

输出为 3。根据https://web.archive.org/web/20170426121932/http://www.cs.cornell.edu/~clarkson/courses/csci4223/2013sp/lec,我假设它是 2 /lec12.pdf

4

2 回答 2

24

PDF 没有完全解释的词汇范围有一个微妙之处。它的示例实际上有两个名为 的不同变量x,它不会重新分配第一个变量的值x(实际上函数式语言可能不允许突变)。

C# 是词法范围的——它在 lambda 的定义点查找x,而不是在调用委托时查找。但是:x解析为一个变量,而不是一个值,它在调用时读取变量的值。

这是一个更完整的示例:

int InvokeIt( Func<int, int> f )
{
   int x = 2;
   return f(1);
}

Func<int, int> DefineIt()
{
   int x = 1;
   Func<int, int> d = (y => x + y);
   x = 3;  // <-- the PDF never does this
   return d;
}

Console.WriteLine(InvokeIt(DefineIt()));

lambda 绑定到内部存在的x 变量。定义点的值 ( ) 无关紧要。该变量稍后设置为。 DefineItx = 1x = 3

但它显然也不是动态范围,因为没有使用x = 2内部InvokeIt

于 2013-04-25T17:13:35.857 回答
23

这个问题是我 2013 年 5 月 20 日博客的主题。谢谢你的好问题!


您误解了“词法范围”的含义。让我们引用您链接到的文档:

函数体是在定义函数时存在的旧动态环境中评估的,而不是在调用函数时的当前环境中评估的。

这是你的代码:

int  x = 1;
Func<int,int> f = y => x + y;
x = 2;
Console.WriteLine(f(1));

现在,什么是“定义函数时存在的动态环境”?将“环境”视为一个类。该类包含每个变量的可变字段。所以这与以下内容相同:

Environment e = new Environment();
e.x = 1;
Func<int,int> f = y => e.x + y;
e.x = 2;
Console.WriteLine(f(1));

f评估时,在创建 f 时存在的环境 e 中x查找。那个环境的内容已经改变了,但是绑定的环境是同一个环境。(请注意,这实际上是 C# 编译器生成的代码!当您在 lambda 中使用局部变量时,编译器会生成一个特殊的“环境”类,并将本地的每次使用转换为字段的使用。)f

让我举一个例子,看看如果 C# 是动态作用域的,世界会是什么样子。考虑以下:

class P
{
    static void M()
    {
        int x = 1;
        Func<int, int> f = y => x + y;
        x = 2;
        N(f);
    }
    static void N(Func<int, int> g)
    {
        int x = 3;
        Console.WriteLine(g(100));
    }
}

如果 C# 是动态作用域的,那么这将打印“103”,因为评估会g计算f,并且在动态作用域语言中,评估会在当前环境中f查找 的值。在当前环境中,是 3。在创建时存在的环境中,是 2。同样,在那个环境中的值已经改变;正如您的文件指出的那样,环境是一个动态环境。但是哪个环境是相关的并没有改变。x xfxx

如今,大多数语言都不是动态范围的,但也有一些。例如,PostScript——在打印机上运行的语言——是动态范围的。

于 2013-04-25T17:39:51.843 回答