编译时间常量和引用类型

时间:2012-01-23 17:12:43

标签: c#

好的,请考虑以下代码:

    const bool trueOrFalse = false;
    const object constObject = null;

    void Foo()
    {
        if (trueOrFalse)
        {
            int hash = constObject.GetHashCode();
        }

        if (constObject != null)
        {
            int hash = constObject.GetHashCode();
        }
    }

trueOrFalse是编译时常量,因此编译器会正确警告int hash = o.GetHashCode();无法访问。

此外,constObject是编译时常量,因此编译器再次正确警告int hash = o.GetHashCode();无法访问o != null永远不会true

那么为什么编译器没有弄清楚:

    if (true)
    {
        int hash = constObject.GetHashCode();
    }

100%肯定是运行时异常,从而发出编译时错误?我知道这可能是一个愚蠢的角落案例,但编译器似乎非常聪明地推理编译时间常量值类型,因此,我希望它也可以找出这个带引用类型的小角落情况。

4 个答案:

答案 0 :(得分:11)

更新:这个问题是he subject of my blog on July 17th 201 2。谢谢你提出的好问题!

  

为什么编译器没有发现我的代码100%肯定是运行时异常,从而发出编译时错误?

为什么编译器会使代码保证会引发编译时错误?不会那样:

int M()
{
    throw new NotImplementedException();
}

进入编译时错误?但这与你想要的完全相反;你希望这是一个运行时错误,以便编译不完整的代码。

现在,您可能会说,取消引用null显然是不可取的,而“未实现”的例外显然是可取的。那么编译器是否可以检测到这种特定情况,即保证发生空引用异常,并给出错误?

当然可以。我们只需花费预算来实现一个数据流分析器,该分析器跟踪已知给定表达式何时始终为null,然后使其成为编译时错误(或警告)以取消引用该表达式。

接下来要回答的问题是:

  • 该功能需要多少费用?
  • 用户累积了多少收益?
  • 是否还有其他可能的功能具有更好的成本效益比,并为用户提供更多值? < / LI>

第一个问题的答案是“相当多” - 代码流分析器的设计和构建成本很高。第二个问题的答案是“不是很多” - 你可以证明将要取消引用null的情况非常少。在过去的十二年里,第三个问题的答案总是“是”。

因此,没有这样的功能。

现在,您可能会说,C#确实有一些有限的能力来检测表达式何时始终/永远为空;可空算术分析器使用此分析生成更优的可空算术代码(*),显然流量分析器使用它来确定可达性。那么为什么不使用已经存在的可空性和流量分析器来检测何时总是取消引用空常量呢?

确实,实施起来很便宜。但相应的用户利益现在很小。实际代码中有多少次将常量初始化为null,然后取消引用它?似乎不太可能有人会这样做。

此外:是的,在编译时而不是运行时检测错误总是更好,因为它更便宜。但是这里的错误 - 保证取消引用null - 将在第一次测试代码时被捕获,并随后被修复。

所以基本上这里的功能请求是在编译时检测一个非常不可能和明显的错误情况,它总是会在第一次运行代码时立即被捕获并修复。因此,将预算用于实施它并不是一个很好的候选人;我们有很多更高的优先事项。


(*)请参阅有关Roslyn编译器如何执行的一系列文章,该文章始于http://ericlippert.com/2012/12/20/nullable-micro-optimizations-part-one/

答案 1 :(得分:4)

虽然无法访问的代码无用且不会影响您的执行,但会执行抛出错误的代码。所以

if (true) { int hash = constObject.GetHashCode();}

或多或少与

相同
throw new NullReferenceException();

你可能想要抛出那个空引用。而无法访问的代码只是在编译时占用空间。

答案 2 :(得分:2)

它也不会警告以下具有相同效果的代码:

throw new NullReferenceException();

警告有平衡。当编译器无法从中产生任何有意义的内容时,会发生大多数编译器错误。

有些事情发生在影响可验证性的事情上,或者超过了它们成为bug的可能性的阈值。例如,以下代码:

private void DoNothing(out string str)
{
  return;
}

private void UseNothing()
{
  string s;
  DoNothing(s);
}

不会编译,但是如果它确实不会造成任何伤害(调用唯一的地方DoNothing不使用传递的字符串,所以从未分配它的事实不是问题) 。风险太高,我在这里做些蠢事让它离开。

警告几乎肯定是愚蠢的,或者至少不是你想要发生的事情。死代码很可能成为一个值得警告的错误,但可能足够明智(例如trueOrFalse可能会随着应用程序的开发而改变)以使错误不合适。

警告是有用的,而不是滋扰,所以它们的标准非常高。没有确切的科学,但它被认为是无法达到的代码,并且在抛出异常时尝试推断并不是理想的行为。

毫无疑问,编译器已经检测到无法访问的代码(并且没有编译它),但无论一方面是多么复杂或另一方面是直接的,都会看到一次故意抛出,就像另一方一样。

答案 3 :(得分:2)

为什么你甚至希望这是一个编译时错误?为什么您希望保证在编译时抛出异常的代码无效?如果我,程序员,我希望我的程序的语义是:

static void Main(string[] args) {
    throw new NullReferenceException();
}

这是我的计划。编译器没有业务告诉我这不是有效的C#代码。