为什么C#中不存在吊装?

时间:2013-09-12 09:40:16

标签: c# javascript hoisting

我每天都使用Javascript和C#,有时我必须考虑使用Javascript时提升。但是,C#似乎没有实现提升(我知道),我无法弄清楚为什么。它更像是一种设计选择还是更类似于适用于所有静态类型语言的安全性或语言约束?

为了记录,我不是说我希望它存在于C#中。我只是想明白为什么不这样做。

编辑:当我在LINQ查询之后声明变量时,我注意到了这个问题,但LINQ查询被推迟到变量声明之后。

    var results = From c In db.LoanPricingNoFee Where c.LoanTerm == LoanTerm
                   && c.LoanAdvance <= UpperLimit Select c
                   Order By c.LoanInstalment Ascending;

    Int LoanTerm = 12;

引发错误,而:

    int LoanTerm = 12;

    var results = From c In db.LoanPricingNoFee Where c.LoanTerm == LoanTerm
                   && c.LoanAdvance <= UpperLimit Select c
                   Order By c.LoanInstalment Ascending;

没有。

5 个答案:

答案 0 :(得分:9)

在我使用的所有编程语言中,Javascript具有最混乱的范围系统,并且提升是其中的一部分。结果是在JavaScript中编写不可预测的代码很容易,你必须小心编写它以使其成为强大而富有表现力的语言。

与几乎所有其他语言一样,C#假定在声明变量之前不会使用变量。因为它有一个编译器,所以如果你试图使用一个未声明的变量,它可以通过简单地拒绝编译来强制执行。在脚本语言中更常见的另一种方法是,如果在未声明的情况下使用变量,则在首次使用时进行实例化。这可能使得遵循代码流程变得有些困难,并且经常被用作对行为方式的语言的批评。大多数使用具有块级范围的语言(其中变量仅存在于声明它们的级别)的人发现它是Javascript的一个特别奇怪的特性。

提升的几个重要原因可能导致问题:

  • 这绝对是违反直觉的,并且使代码更难阅读,并且除非您意识到这种行为,否则其行为更难以预测。难以阅读和难以预测的代码更有可能包含错误。
  • 在限制代码中的错误数量方面,限制变量的生命周期可能非常有用。如果你可以声明变量并在两行代码中使用它,那么在这两行之间有十行代码会给意外影响变量行为带来很多机会。 代码完成中有很多相关信息 - 如果你还没有读过,我衷心推荐它。
  • 有一个the Principle Of Least Astonishment的经典UX概念 - 像吊装(或者像Javascript处理相等的方式)这样的功能往往会破坏它。在开发编程语言时,人们通常不会考虑用户体验,但实际上程序员往往是非常挑剔的用户,当他们发现自己常常被奇怪的功能所困扰时,他们会有点脾气暴躁。 Javascript非常幸运,它在浏览器中的独特无处不在创造了一种强制普及,这意味着我们必须容忍它的许多怪癖和有问题的设计决策。

最后,我无法想象为什么它会成为像C#这样的语言的有用补充 - 它可以带来什么样的好处?

答案 1 :(得分:8)

“它更像是一种设计选择,还是更类似于适用于所有静态类型语言的安全性或语言约束?”

这不是静态类型的约束。编译器将所有变量声明移动到作用域的顶部(在Javascript中这是函数的顶部,在C#中是当前块的顶部)并且如果使用不同类型声明名称时出错将是微不足道的。

因此,C#中不存在提升的原因纯粹是一个设计决策。为什么它的设计方式我不能说我不在团队中。但这可能是因为如果在使用之前总是声明变量,那么解析的简易性(对于人类程序员和编译器都是如此)。

答案 2 :(得分:4)

Loop-invariant code motion的上下文中存在一种存在于C#(和Java)中的Hoisting形式 - 这是JIT编译器优化,它从循环语句中“提升”(提取)表达式影响实际循环。

您可以详细了解here

引用:

  

“Hoisting”是一种编译器优化,可以移动循环不变代码   没有循环。 “循环不变代码”是引用的代码   对循环是透明的,可以用它的值替换,这样   它不会改变循环的语义。此优化得到改善   通过执行代码只执行一次而不是执行运行时性能   每次迭代。

所以这个编写的代码

public void Update(int[] arr, int x, int y)
{
    for (var i = 0; i < arr.Length; i++)
    {
        arr[i] = x + y;
    }
}

实际上已经过优化,有点像这样:

public void Update(int[] arr, int x, int y)
{
    var temp = x + y;
    var length = arr.Length;
    for (var i = 0; i < length; i++)
    {
        arr[i] = temp;
    }
}

这种情况发生在JIT中 - 即将IL转换为本机机器指令时,不容易查看(您可以查看herehere)。

我不是阅读汇编的专家,但这是我使用BenchmarkDotNet运行此片段所得到的,我对它的评论显示优化实际发生了:

int[] arr = new int[10];
int x = 11;
int y = 19;

public void Update()
{
    for (var i = 0; i < arr.Length; i++)
    {
        arr[i] = x + y;
    }
}

生成:

enter image description here

答案 3 :(得分:3)

因为它是一个错误的概念,很可能因为急于实现JavaScript而存在。这是一种糟糕的编码方法,即使是经验丰富的javascript编码器也会误导变量的范围。

答案 4 :(得分:2)

函数提升在编译器必须完成的工作中可能存在不必要的成本。例如,如果由于各种代码控制决策返回函数而从未达到变量声明,则处理器不需要浪费时间将未定义的空引用变量推送到堆栈内存,然后将其从堆栈中弹出作为这是方法的清理操作,甚至没有达到。

另外,请记住JavaScript具有“变量提升”和“功能提升”(以及其他),这些处理方式不同。函数提升在C#中没有意义,因为它不是自上而下的解释语言。编译代码后,可能永远不会调用该方法。但是,在JavaScript中,当解释器解析它们时,会立即评估“自调用”函数。

我怀疑这是一个任意的设计决定:不仅提升C#效率低下,而且对C#的工作方式也没有意义。