【问题标题】:Why doesn't hoisting exist in C#?为什么 C# 中不存在提升功能?
【发布时间】:2013-09-16 15:06:08
【问题描述】:

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

没有。

【问题讨论】:

  • 这样做有什么好处?它将如何处理块级范围?
  • 我在 LINQ 查询之后声明变量时注意到了这个问题,但是 LINQ 查询被推迟到变量声明之后。
  • 您的代码示例是 Visual Basic 而不是 C#,如果我没记错的话,如果使用了尚未声明的名称,它将自动声明一个变量。您发布的第一个代码无法在 C# 下编译。
  • 抱歉,这是 VB.NET。但是上面的不编译,下面的可以编译,和C#一样。
  • 如果那个类中有一个叫LoanTerm的字段,而且C#也支持提升呢?你期望什么输出? scope 的概念也必须更改以匹配 Javascript,以提供任何合理的行为。

标签: c# javascript hoisting


【解决方案1】:

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

【讨论】:

  • 您能解释一下为什么这是一个错误的概念吗?或者也许指向我一篇关于为什么会这样的文章?
  • @BenM: 1. 声明变量 2. 使用它。按此特定顺序
  • @zerkms 这并不能解释为什么提升是一个错误的概念。
  • @BenM:如果他们愿意,c# 开发团队可以设计它。你问“为什么没有这样的功能”的问题是不正确的。正确的应该是“为什么他们会希望这个可怕的东西用如此优雅的语言来表达”。如果证明它有用,就会添加该语言的功能。提升有多大用处(在 C# 或任何其他语言中)?
  • @BenM:使用它的原因是什么?人们做出一些设计决定是因为它真的很有必要,而不是因为“为什么不”。如果你遵循“为什么不”的设计原则,你最终会得到一种像 javascript 这样的语言。
【解决方案2】:

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

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

吊装可能导致问题的两大原因:

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

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

【讨论】:

  • 我完全同意。我更喜欢不使用提升,这当然不是我想在 C# 中看到的东西。很高兴了解其背后的原因。
【解决方案3】:

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

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

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

【讨论】:

  • 谢谢!这正是我想要的。
  • "将所有变量声明移动到函数的顶部" --- 到作用域的顶部。
  • @BenM:“为什么它是这样设计的,我不能说”“这正是我想要的”facepalm :-S
  • @zerkms:这是一个赌注,这家伙刚赢了 10 美元。
  • @zerkms,Ibasa 清楚、简洁地回答了我的问题,并且直截了当,他没有花很长时间问我“为什么要在 C# 中使用它,这太糟糕了”等等。我很清楚这一点,我很好奇它背后的原因,我不介意个人对它为什么如此糟糕的看法。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

    您可以通过here了解更多信息。

    引用:

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

    所以写了这段代码

    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 运行这个 sn-p 得到的结果,我的 cmets 显示优化确实发生了:

    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;
        }
    }
    

    生成:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-16
      • 1970-01-01
      • 1970-01-01
      • 2018-05-29
      • 1970-01-01
      相关资源
      最近更新 更多