This morning I happened to show a colleague (Malcolm Rowe) the neat trick of using nullable types and the null-coalescing operator (??) to implement compound comparisons in C#. He asked whether it wouldn’t have been nicer to make this a library feature rather than a language feature. I’m all for putting features into libraries where possible, but there’s a problem in this case: the ?? operator doesn’t evaluate its right operand unless the left operand evaluates to
null. This can’t be replicated in a library. Or can it?
The obvious way to lazily evaluate an expression is to turn it into a closure. So, we can write out coalescing method as:
return lhs != null ? lhs : rhs();
That’s quite an efficient way of doing it, but the assymetry isn’t ideal. We can fix that by making the first argument a function too:
T first = lhs();
return first != null ? first : rhs();
One of the nice things you can do with the null-coalescing operator is use it for multiple expressions, e.g.
a ?? b ?? c ?? d which will evaluate
a, then (if it’s null) evaluate
b, then (if that’s null) evaluate
c etc. With the symmetry present we can now make this into a parameter array:
T current = default(T);
foreach (Func<T> func in functions)
current = func();
if (current != null)
(In some ways it’s still more elegant to specify a bare
T as the first parameter, as that ensures that at least you’ve got one function to call. The change required is pretty obvious.)
Now there are only two problems: invoking the method, and the performance characteristics. I’m going to ignore the latter – yes, you could end up with a significant performance hit creating all these closures all the time if you use this in performance critical code. There’s not a lot of options available there, really. But what about the syntax for invoking the method?
In its current form, we can write the current
a ?? b ?? c ?? d as
CoalesceNulls(() => a, () => b, () => c, () => d)l. That’s a wee bit ugly. Malcolm blogged about an alternative where the parameters could be declared on the declaration side) as calling for deferred execution, and automatically converted into closures by the compiler.
Just like Malcolm, I’m not terribly keen on this – for instance, I really like the fact that in C# it’s always very clear when a parameter is being passed by reference, because there’s the
ref keyword on the caller side as well as the declaration side. Going against this would just feel wrong.
If we have to change the language and introduce new keywords or symbols, we’re then back where we started – the whole idea was to avoid having to introduce ?? into the language. The plus side is that it could be “usage agnostic” – just because it could be used for a null-coalescing operator replacement doesn’t mean it would have to be. However, this feels like using a sledgehammer to crack a nut – the number of times this would be useful is pretty minimal. The same argument could be applied to the null-coalescing operator itself, of course, but I think its utility – and the way it fits into the language pretty seamlessly – justifies the decisions made here.
Still, it was an interesting thought experiment…
Update: Since originally writing this (a few days ago, before the blog outage, Malcolm has been informed that Scala has precisely this capability. Just another reason for getting round to learning Scala at some point…