``??`` 运算符是否使用短路求值?

8

?? 运算符在 C# 中计算时是否使用短路运算?

var result = myObject ?? ExpressionWithSideEffects();

myObject不为null时,ExpressionWithSideEffects()的结果不会被使用,但是ExpressionWithSideEffects()会完全被跳过吗?
3个回答

10

是的,它会短路。

这里有一段代码片段可以在 LinqPad 中测试:

string bar = "lol";
string foo = bar ?? string.Format("{2}", 1);
foo.Dump();
bar = null;
foo = bar ?? string.Format("{2}", 1);
foo.Dump();

第一个 coalesce 在不抛出异常的情况下工作,而第二个则会抛出异常(格式字符串无效)。


糟糕,我感觉自己被拉进了事件视界! - user1228

7
是的,它可以。像往常一样,C#语言规范是权威的来源。1 从C# 3规范,第7.12节(v4规范包含不相关的动态细节):
表达式a ?? b的类型取决于操作数类型之间可用的隐式转换。按优先级顺序,a ?? b的类型是A0、A或B,其中A是a的类型,B是b的类型(前提是b有类型),如果A是可空类型,则A0是A的基础类型,否则为A。具体来说,a ?? b的处理如下:
1. 如果A不是可空类型或引用类型,则会发生编译时错误。
2. 如果A是可空类型,并且从b到A0存在隐式转换,则结果类型为A0。在运行时,首先计算a。如果a不为null,则将a拆开成类型A0,并成为结果。否则,计算并将b转换为A0类型,并将其作为结果。
3. 否则,如果从b到A存在隐式转换,则结果类型为A。在运行时,首先计算a。如果a不为null,则a成为结果。否则,计算并将b转换为类型A,并将其作为结果。
4. 否则,如果b具有类型B,并且从A0到B存在隐式转换,则结果类型为B。在运行时,首先计算a。如果a不为null,则将a拆开成类型A0(除非A和A0是相同类型),并转换为类型B,这将成为结果。否则,计算并成为结果。
第二、三和四个项目是相关的。
注:有关使用的编译器是否是实际的真相源头还存在一些哲学上的讨论……一个语言的真相是其应该做什么,还是当前的做法

至于脚注... 我想这就是我们都喜欢埃里克·利珀特出现的原因 :) - Matthew Whited
1
@Matthew:是的,这是许多原因中的一个。Eric有趣之处在于他可以作为规范和编译器的人类化身。 - Jon Skeet

0
这就是为什么我们需要单元测试。
    [TestMethod]
    public void ShortCircuitNullCoalesceTest()
    {
        const string foo = "foo";
        var result = foo ?? Bar();
        Assert.AreEqual(result, foo);
    }

    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void ShortCircuitNullCoalesceFails()
    {
        const string foo = null;
        var result = foo ?? Bar();
    }

    private static string Bar()
    {
        throw new ArgumentException("Bar was called");
    }

这些不是最好的测试名称,但你可以理解它。它展示了空合并运算符按预期短路的情况。


我意识到ArgumentException是一个奇怪的选择,只是第一个想到的异常类型。 - CaffGeek
3
我们进行单元测试不是出于这个原因,而是因为我们有语言规范。特别是,如果我们只进行单元测试但没有语言规范,我们将仅了解在测试中发生的情况。然而,如果我们有了语言规范但没有单元测试,我们仍然知道语言在一般情况下应该做什么。诚然,单元测试有助于验证编译器是否实现了语言规范......但对于这样的问题,我总是更倾向于查阅规范而不是单元测试。 - Jon Skeet
@Jon Skeet,说得好。我仍然喜欢编写快速测试以验证我不确定的事情。我不一定会保留它。而且编译器实现规范不当的可能性总是存在... - CaffGeek
编译器实现规范可能不正确 - 只是你偶然选择的情况而已。事实上,我怀疑在简单的情况下比在复杂难懂的情况下更容易正确处理 :) - Jon Skeet
@Jon Skeet,毫无疑问,我同意这将是一个难以搞砸的测试...而且在编写完成后,我也不会保留此测试来学习编译器的工作方式(这比阅读文档更快)。但要重申一句最喜欢的话:“不要被注释所迷惑 - 它们可能会非常具有误导性。” - CaffGeek

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接