我认为 C# 具有词法范围,但为什么此示例显示动态范围行为

本文关键字:范围 显示 动态 为什么 词法 | 更新日期: 2023-09-27 18:10:58

    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/lec12.pdf,我假设它是 2

我认为 C# 具有词法范围,但为什么此示例显示动态范围行为

关于词汇范围有一个微妙之处,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 绑定到存在于 DefineIt 中的 x 变量。 定义点的值(x = 1(是无关紧要的。 该变量稍后设置为 x = 3

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

这个问题是我在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 时,x 将在创建 f 时存在的环境 e 中查找。 该环境的内容已更改,但f绑定到的环境是同一环境。(请注意,这实际上是 C# 编译器生成的代码!当您在 lambda 中使用局部变量时,编译器会生成一个特殊的"环境"类,并将局部变量的每个用法转换为字段的用法。

让我举一个示例,说明如果 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将在当前环境中查找x的值。在当前环境中,x为 3。在创建 f 时存在的环境中,x为 2。同样,x在这种环境中的价值发生了变化;正如您的文档所指出的,环境是一个动态环境。但是哪个环境是相关的不会改变。

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