Writing

Other Sites

Nulls and Lifting Member Access

Sunday 13 April, 2008, 05:30 PM

Nulls can complicate code. One of the lesser known C# language features, the ?? operator, can sometimes help. This operator lets you provide an alternate value to be used in the event that an expression evaluates to null. For example:

string displayName = elem.Name ?? "[Unknown]";

If elem.Name evaluates to anything other than null, then displayName will be elem.Name. But if elem.Name is null, displayName will become "[Unknown]". To get the same result in C# 1 requires the cryptic but useful ternary operator (aka conditional operator) (‘Confusing C family language newbies since 1978’). The result is longer, and requires you to write out the expression twice:

string displayName = elem.Name == null ? "[Unknown]" : elem.Name;

In my experience, most C# developers don’t seem to know about ??. In my Applied WPF course, one of the labs uses it in passing, and I invariably get people either asking me what it does, or occasionally telling me it’s a typo!

People usually like it once they get it, because it’s a clear improvement for this sort of example. So why isn’t it better known? I have a theory: I think it’s less useful than it seems. It only solves one specific example of the ‘nulls are a pain’ class of problems. Most of the time, I want something slightly more powerful than ??.

What I’d Like

While I frequently have to deal with values that might be null, I’ve found that in the non-null case I usually want to work with a member of the object, rather than the object itself. For example, consider this very simple singly-linked list node:

publicclassNode
{
publicNode Next;
publicstring Value;
}

Suppose you have a variable of type Node which may be null, and that you want to return the Value if the variable’s non-null, and return null otherwise. You can’t use the ?? operator here – you have to revert to the good old ternary operator:

Node n = GetNode();
string value = n == null ? null : n.Value;

This is tolerable, but it’s a case that comes up often enough that I can see the value in having a special syntax to handle it. Sadly, the ?? operator is not that syntax – it only seems to cover a small minority of the cases.

A different solution suggests itself when you look at another feature added in C# 2: lifting for nullable types.

Lifting

Lifting in C# is a convenient way of working with values that might be null: rather than crashing when you try and use a null value, the nullness just ends up propagating. For example, C# supports lifting when adding numbers together:

publicstaticint? Add(int? x, int? y)
{
return x + y;
}

If you call this function with integers, it will add them as expected. But you can pass in null for either argument, in which case the result will be null. So we say that the + operator is lifted. (See Eric Lippert’s description of lifting in C# for more information.)

Not all operators are lifted in C#. The arithmetic ones are, so you can perform numeric calculations with nullable types. But many are not. Member access is not lifted, for example. If it were, I’d be able to replace this:

string value = n == null ? null : n.Value;

with this:

string value = n.Value;

If ‘.’ were lifted, value would end up as null if either n itself were null, or the Value member of the object referred to by n were null.

This seems like a much more succinct way to deal with the problem, but of course that’s not what ‘.’ does in practice. Morever, we cannot overload ‘.’ in C#, so on the face of it, it seems that only the C# compiler team would be able to make this work. However, we can achieve something close by getting the relevant code into expression tree form, and then modifying the tree.

Expression Tree Rewriting

(This idea is pretty similar to LISP’s macro system – code rewriting expressions or generating new ones. There’s a significant difference though: LISP macros run at compile time. As far as I can tell, we don’t have that option with expression trees in C# 3. However, even though we are obliged to wait until runtime to perform the tree rewriting, we do get to compile the result into IL. So after the initial overhead, the performance should be identical to normally compiled code.)

One slight snag is that expression trees are immutable. So you can’t just change them – you end up having to build a new tree. Fortunately, Microsoft provides an example class in the documentation that helps you do this: ExpressionVisitor. This walks every node in an expression tree, and lets you build an edited copy. And it’s smart about unchanged subtrees – it just reuses those rather than copying them. (That’s one of the advantages of immutability – it’s safe to reuse objects like that because you know they’re not going to change.)

The following class derives from the ExpressionVisitor example, and overrides VisitMemberAccess to change the way member access works:

The call to the base class builds the expression we’re about to rewrite (possibly recursively rewriting some of its sub expressions.) We then generate some extra expressions to test the target object reference for null, and then to return either a null constant or the original expression, depending on whether the target reference is null. Or to put it in code, it changes this:

obj.Member

to this:

obj == null ? null : obj.Member

For ease of use, we can wrap this rewriter in a helper function that takes an expression, rewrites it, and then compiles the result:

So we have four methods, which pick out the value of the current node, the next node, the node after that, and finally the node after that. I’m using the ordinary member access syntax, but I’m calling the code I wrote earlier to add lifting to the ‘.’ operator. To test these methods, I wrote the following (which, just for fun/added confusion, uses the ?? operator to help print out the results):

This builds a list with three nodes, and iterates through this list, trying all four get methods on each of the nodes.

Normally, you’d expect this to throw a null reference exception. If you have a reference ‘f’ to the first node, the expression f.Next.Next.Next.Value is looking for the fourth node (i.e. three along from the first). There are only three nodes, so that final Next will evaluate to null. Normally you’d expect the attempt to access Value to throw an exception. But we’ve run these expressions through my lifting rewriter, so instead, we see this:

This illustrates that the lifting is working as desired. This is consistent with the + operator: adding null to a number gives you null; likewise here, when I try to access a member via a null reference I get back null.

Limitations

One big problem with this is that it’s pretty clunky. I have to wrap up the member access in a lambda, and then pass it to my function to get it rewritten and compiled. If I want to make sure I perform that step just the first time my code runs, and yet I still want my lambdas to have access to local variables in scope, the code’s going to get even uglier. So while it might be a neat trick to illustrate the idea, I don’t see myself using this in production code.

This is a shame, because the idea of rewriting expressions is a pretty powerful one. Lifting member access is only one example of what you can do with this technique. It would be great if there were a clean syntax for this sort of idea.

(Also, beware that I’ve not tested this in any serious way – it’s only intended to illustrate an idea. It’s probably full of subtle flaws as well as the more glaring issues. I suspect it doesn’t play nicely with Nullable<T>, for example. Please don’t use it as is in your production code! That said, I offer this code under the MIT license, so feel free to do what you will with it within the terms of the license. Just consider yourself warned. You can grab the source from here: http://www.interact-sw.co.uk/downloads/LiftMemberAccess.zip)

A Monadic Footnote

When I first started to look at this, I had been considering a slightly different approach. The idea of a nullable value (whether it’s a Nullable<T> or just an ordinary reference) is pretty similar to Haskell’s Maybe type. I can use Maybe to write a Haskell equivalent to the Add function I wrote in C# above:

This is slightly long-winded, because in Haskell, the addition operator is not suitably lifted – it doesn’t recognize Maybe types. (I could arrange for this lifting to happen in Haskell by plugging the code above into the right place and doing some other extra work, incidentally. But this entry’s long enough already.) So I had to write code to explicitly extract the values. A slightly closer C# version might look like this:

However, this C# doesn’t work, because we’ve lost the lifting – it’ll throw an exception if you pass in null for either argument. But the Haskell version works fine, as we can see from this interactive GHCi session:

(Note, ‘Just’ is a constructor for a Maybe instance. So (Just 18) is roughly similar to new Nullable<int>(18) in C#. And Nothing constructs a Maybe that contains no value – it’s more or less like null.)

This works because Maybe belongs to Haskell’s Monad type class. The syntax involving ‘do’ and ‘<-’ is available on monadic types, and in the case of the Maybe monad, the <- operator provides lifted access to the value inside the Maybe – if either x or y turn out to contain Nothing then that whole ‘do’ block will evaluate to Nothing. (See this allegedly ‘gentle’ introduction to monads for more information on monads in Haskell.)

Not only does this follow a fairly similar pattern to the Haskell version, it would also offer the same lifting behaviour – it would work correctly in the face of Nothing (null) values.

So it occurred to me that I could use this technique to perform the member access lifting I was looking for. Here’s a class that tries to do this by providing some extension methods that effectively define the monadic ‘bind’ operator for references:

The SelectMany implementation here just bails out early if it hits a null, and returns null without attempting to evaluate anything that follows. (And Select does much the same.) Note: I’ve used the phrase ‘maybe’ here to indicate potentially empty values – I’m not actually using Wes’s Maybe implementation. This particular code snippet stands alone.

Again, I would recommend NOT using this in production code – it effectively adds standard LINQ query operators to System.Object, which is probably not a sane thing to do in a real program. But with just that code (I don’t need any of the code from earlier) I can now write this sort of thing:

int? two = 2;
int? one = 1;
int? none = null;
var twoPlusTwo = from x in two
from y in two
select x + y;
Console.WriteLine(twoPlusTwo);
var onePlusNothing = from x in one
from y in none
select x + y;
Console.WriteLine(onePlusNothing);
var nothingPlusTwo = from x in none
from y in two
select x + y;
Console.WriteLine(nothingPlusTwo);
var onePlusTwoPlusOne = from x in one
from y in two
from z in one
select x + y + z;
Console.WriteLine(onePlusTwoPlusOne);

This prints out results for the calculations that have non-null results, and blank lines for the others. (Console.WriteLine just prints an empty line when given a null.) So lifted use of nullable types is working. But we can also use this to deal with member access through potentially null references.

The simplest example of working with member access wouldn’t work if I had only provided the SelectMany method. I added Select as well was so it would work even if there were only one from clause. That lets me write this sort of thing:

This provides a solution of sorts to the member access problem we started with. I’ve run into this particular example on many occasions – I’ve used an XPath query against an XmlDocument and need to deal with the fact that some attributes may be missing on some of the elements that came back. (And yes, I could have written a more selective XPath query in this particular case. But when you’re dealing with lots of optional attributes, that gets tedious.) If the ‘title’ attribute is missing on a particular item, the code won’t hit an exception on the attr.Value expression, because my implementation of Select chooses not to evaluate the select clause at all if attr is null. (And the presence of SelectMany means the same will be true if I use multiple from clauses.)

So it’s a similar result to the expression-tree-based lifting of the ‘.’ operator: I get to write code that attempts to access members via references that may be null without needing an explicit null check. However, the resulting code looks pretty arcane, so again, I don’t think I’d use this in production code. In practice, the problem in this particular example is better solved in C# with an ordinary helper function.

Conclusion

I’ve used some tricks here to implement a couple of variations on my original theme: trying to avoid writing explicit tests for null references when accessing member variables, by lifting member access over null. Unfortunately, neither solution is pretty, so I consider this experiment an interesting failure. And perhaps that’s to be expected – I’m trying to subvert a fundamental feature of the language that was never designed to be modified. If C# had wanted to make it easy, either ‘.’ would have been overloadable, or the compiler would support compile-time hygienic macros.

Would I want to see either of those things in C#? I’m not sure I would – I’ve suffered too much ‘clever’ C++ that tries to redefine language behaviours. To be fair, a lot of the horror there was down to how the clunky macro system in C++ is entirely unrelated to the rest of the language. However, some of the horror was with better integrated features, such as the way you can in fact overload member access in C++.

There’s a lot to be said for a language where code does what it appears to do. It’s normal to spend more time reading code than writing it, and if you need to question fundamentals such as “is that thing that looks like field access really accessing a field?” every time you look at a line of code, that’s going to slow you down massively, and increase the chances of misunderstanding the code.

It reminds me of a troubleshooting consulting engagement I had years ago. A memory leak turned out to be the result of a couple of ‘clever’ behaviours interacting badly. I still remember the growing expression of disbelief on the developer’s face as I stepped into what looked like a single, straightforward statement, but which turned out to involve no fewer than seven implicit function calls! He had no idea it was doing all that, and he’d written it!

If I were going to have a syntax that means “get this thing from that object, unless we have a null reference, in which case return null”, I think I’d prefer a new, specialized syntax over a new behaviour for an established syntax.