6

我每天都使用 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;

才不是。

4

5 回答 5

11

在我使用过的所有编程语言中,Javascript 具有最令人困惑的作用域系统,而提升是其中的一部分。结果是用 JavaScript 编写不可预知的代码很容易,您必须小心编写它以使其成为强大且富有表现力的语言。

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

吊装可能导致问题的几个重要原因:

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

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

于 2013-09-12T10:01:18.930 回答
9

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

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

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

于 2013-09-12T10:01:55.923 回答
6

在循环不变代码运动的上下文中,C#(和 Java)中存在一种提升形式- 这是 JIT 编译器优化,它从不影响实际的循环语句中“提升”(拉起)表达式环形。

您可以在此处了解更多信息。

引用:

“提升”是一种编译器优化,可以将循环不变的代码移出循环。“循环不变代码”是指对循环透明的代码,可以用它的值替换,这样它就不会改变循环的语义。这种优化通过只执行一次代码而不是每次迭代来提高运行时性能。

所以这个写的代码

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 翻译为本机机器指令时,因此不太容易查看(您可以在此处此处查看)。

我不是阅读汇编的专家,但这是我使用 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;
    }
}

生成:

在此处输入图像描述

于 2018-06-13T10:03:19.383 回答
2

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

于 2013-09-12T09:46:49.950 回答
2

Function hoisting has a potentially unnecessary cost in work that the compiler has to fulfill. For example, if a variable declaration is never even reached because various code control decisions returned the function, then the processor does not need to waste time pushing an undefined null-reference variable onto the stack memory and then popping it from the stack as part of it's method's clean up operations when it wasn't even reached.

Also, remember that JavaScript has "variable hoisting" and "function hoisting" (among others) which are treated differently. Function hoisting wouldn't make sense in C# since it is not a top-down interpreted language. Once the code is compiled, the method might not ever be called. In JavaScript, however, the "self-invoking" functions are evaluated immediately as the interpreter parses them.

I doubt that it was an arbitrary design decision: Not only is hoisting inefficient for C#, but it just wouldn't make sense for the way that C# works.

于 2017-04-28T18:13:51.410 回答