You may be asking the wrong question. You don't choose to use one over the other primarily due to efficiency (though it may be a secondary concern), but due to utility.
Really you should compare ??
to ?:
and not to if
as they have different purposes. Yes, they are all some form of "conditional" goodness, but the key is that both ??
and ?:
evaluate to a value, whereas if
does not, thus they often have different uses.
For example, the following code:
Console.WriteLine("The order} is for {1} product",
orderId, productId ?? "every");
Would be clunkier to write with an if
:
if (productId == null)
{
Console.WriteLine("The order {0} is for every product",
orderId);
}
else
{
Console.WriteLine("The order {0} is for {1} product",
orderId, productId);
}
Yes, you could condense to one, but then you'd have a temp variable, etc:
if (productId == null)
{
productId = "every";
}
Console.WriteLine("The order {0} is for {1} product",
orderId, productId);
Thus really, you shouldn't compare the two, because they point of the ??
is to evaluate to a value if the argument is null
, whereas the point of if
is to execute a different path (not directly resulting in a value.
So, a better question may be, why not this instead:
Console.WriteLine("The order {0} is for {1} product",
orderId, productId == null ? "every" : productId);
Which is much the same (both evaluate to a value) and are not as much for flow control.
So, let's look at the difference. Let's write this code three ways:
// Way 1 with if
string foo = null;
string folder = foo;
if (folder == null)
{
folder = "bar";
}
// Way 2 with ? :
string foo2 = null;
var folder2 = foo2 != null ? foo2 : "bar";
// Way 3 with ??
string foo3 = null;
var folder3 = foo3 ?? "bar";
For the IF, we get the following IL:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: ldnull
IL_0005: ceq
IL_0007: ldc.i4.0
IL_0008: ceq
IL_000A: stloc.1
IL_000B: ldloc.1
IL_000C: brtrue.s IL_0016
IL_000E: nop
IL_000F: ldstr "bar"
IL_0014: stloc.0
For the conditional (? :) we get the following IL:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: brtrue.s IL_000D
IL_0006: ldstr "bar"
IL_000B: br.s IL_000E
IL_000D: ldloc.0
IL_000E: nop
IL_000F: stloc.1
For the null-coallescing (??) we get this IL:
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldloc.0
IL_0004: dup
IL_0005: brtrue.s IL_000D
IL_0007: pop
IL_0008: ldstr "bar"
IL_000D: stloc.1
Notice how each successive one is simpler? The if
is larger IL because it needs branching logic to handle the separate statements. The ?:
is smaller because it simply evaluates to a value (not branching to other statements) but still needs to load the operand to compare to (null
).
The ??
is the simplest of all, because there is an IL instruction for comparing to null
(vs loading null
and comparing to it.
SO all of this said, you're talking a very small difference in terms of IL, which may or may not affect performance. Regardless, chances are this will have very little major difference compared to more intensive work in the program (math, database, network, etc.).
Thus, I would suggest choose the one that is the most readable, and only optimize if you find through profiling that your current method is inadequate and a bottleneck.
To me, the real reason to use ?:
or ??
is when you want the end result to be a value. That is, anytime you'd be tempted to write:
if (someCondition)
x = value1;
else
x = value2;
Then I'd use the conditional (?:
) because that is what it is a great shorthand for. x
gets one or the other value based on this condition...
Then I'd go one further with ??
and say the same is true, you want to assign a value to a variable based on the null
-ness of an identiifer.
So if
is great for flow-control, but if you are just returning one of two values or assigning one of two values based on a condition, I'd use ?:
or ??
as appropriate.
Finally keep in mind how these things are implemented under the covers (IL and the associated performance) are subject to change with each revision of the .NET Framework (as of me writing this right now they are all so close to be negligible).
Thus, what may be faster today may not be faster tomorrow. So again, I'd just say go with the one that fits best and you find most readable.
UPDATE
Incidentally, for the truly obsessed, I compared 10,000,000 iterations of each code swatch above, and here's the total time to execute each. Looks like ??
is fastest for me, but again these are so close to be almost inconsequential...
10,000,000 iterations of:
?: took: 489 ms, 4.89E-06 ms/item.
?? took: 458 ms, 4.58E-06 ms/item.
if took: 641 ms, 6.41E-06 ms/item.