views:

162

answers:

3

Does the ?? operator in C# use shortcircuiting when evaluating?

var result = myObject ?? ExpressionWithSideEffects();

When myObject is non-null, the result of ExpressionWithSideEffects() is not used, but will ExpressionWithSideEffects() be skipped completely?

+10  A: 

Yes, it does short circuit.

Here's a snippet to test in LinqPad:

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

The first coalesce works without throwing an exception while the second one does throw (the format string is invalid).

Will
crap, I can feel myself being pulled into the event horizon!
Will
A: 

This is why we have unit testing.

    [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");
    }

These aren't the best test names, but you get the idea. It shows that the null coalesce operator short circuits as expected.

Chad
And I realize ArgumentException was an odd choice, it was just the first exception type to spring to mind.
Chad
This isn't why we have unit testing. This is why we have language specifications. In particular, if we had unit testing but no language spec, we would only know what it happens to do in the case being tested. If we had the language spec but no unit testing, however, we would still know what the language is meant to do in the general case. Admittedly unit testing helps to verify that the compiler actually implements the language spec... but I'd always rather reach for the spec than a unit test for questions like this.
Jon Skeet
@Jon Skeet, touche. I still like writing quick tests to verify things I am not sure about. I won't necessarily keep it around. And it's always possible that the compiler implemented the spec improperly...
Chad
@Chad: It's also possible that the compiler implemented the spec improperly - just not for the case you happened to pick. In fact, I suspect that it's much more likely to get *simple* cases right than obscure ones :)
Jon Skeet
@Jon Skeet, for sure, I do agree, this would be a tough one to screw up... and I wouldn't keep this test around after writing it to learn how the compiler is working (it's quicker than reading docs). But to repeat a favourite quote "Don't get suckered in by the comments - they can be terribly misleading."
Chad
+6  A: 

Yes it does. As ever, the C# language specification is the definitive source1.

From the C# 3 spec, section 7.12 (v3 rather than 4, as the v4 spec goes into dynamic details which aren't really relevant here):

The type of the expression a ?? b depends on which implicit conversions are available between the types of the operands. In order of preference, the type of a ?? b is A0, A, or B, where A is the type of a, B is the type of b (provided that b has a type), and A0 is the underlying type of A if A is a nullable type, or A otherwise. Specifically, a ?? b is processed as follows:

  • If A is not a nullable type or a reference type, a compile-time error occurs.
  • If A is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0, and this becomes the result. Otherwise, b is evaluated and converted to type A0, and this becomes the result.
  • Otherwise, if an implicit conversion exists from b to A, the result type is A. At run-time, a is first evaluated. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and this becomes the result.
  • Otherwise, if b has a type B and an implicit conversion exists from A0 to B, the result type is B. At run-time, a is first evaluated. If a is not null, a is unwrapped to type A0 (unless A and A0 are the same type) and converted to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
  • Otherwise, a and b are incompatible, and a compile-time error occurs.

The second, third and fourth bullets are the relevant ones.


1 There's a philosophical discussion to be had about whether the compiler you happen to be using is the actual source of truth... is the truth about a language what it's meant to do or what it currently does?

Jon Skeet
To the foot note... I think that's why we all enjoy Eric Lippert being around :)
Matthew Whited
@Matthew: One of the many reasons, yes. One interesting aspect of Eric is that he can act as the human incarnation of both the spec *and* the compiler...
Jon Skeet