(You are currently looking at the first edition version of this page. This page is also available for the second and third editions.)

Notes for Chapter 4: Saying nothing with nullable types

4.1.2: Magic value traps

Joe Albahari pointed out that another problem with magic values is that it's easy to forget to check for them:

There's nothing to stop you from accidentially treating it like a real value, and performing arithmetic on it, causing an error, potentially way down the line. If you make the same mistake with a nullable type, you'll get an exception thrown right away. This is probably the most convincing argument for the usefulness of nullable types over magic values.

4.2.1: Mutable value types in the framework

Joe Albahari points out that there are a few mutable value types in the framework - in the System.Drawing namespace, Point, Size and Rectangle are all mutable. The same choices have been made in WPF, too.

Use with care.

4.2.3: Equality of nulls

As the book mentions, all the various operators are language-specific. Equality is a particularly good example. In VB, x=y (as an equality comparison, not an assignment) has a nullable result when x or y are nullable. If both sides are null values, then the result is null.

The language designers decided that was one step too far for C#. Which is the best approach? It's very hard to say. I'm sure that in some scenarios the C# way is clearer, and in others the VB way is clearer.

It pains me to cede the point, but in this particular case I think that VB has more purity and integrity than C#. Just don't remind me about it too often :)

4.3.1: Non-nullable reference types

Just for kicks, imagine a world where all types were non-nullable by default. Sure, the null reference is available when required, but suppose it were prohibited unless you'd explicitly said you'd want to allow it. Any attempt to return a value which might be null could be prohibited in a method declared to return just string, say - whereas if the method were declared to return string? it would be okay.

An alternative which is still possible would be to use ! as a "non-null reference type" modifier. So a method with a signature of void Foo(string! x) would automatically enforce that x wasn't null, etc.

Is it worth it at this point? Probably not - it's one of those ideas which is only really workable if it's present right at the start. There's too much code "out there" now.

4.3.2: Happy birthday, Donald Knuth

As it happened, Eric reviewed listing 4.4 on January 10th, 2008. He left a note in the margin wishing Donald Knuth a happy 70th birthday. I don't know if a card was sent, however.

4.3.3: Lifted or not?

It's unfortunate, but the C# specification is inconsistent in its use of the word lifted - at least at the moment.

Eric has blogged about this very issue - and I'm proud to reveal that I'm the reader mentioned in the post :)

4.3.3: Unexpected conversion

Ross Bradbury posted an interesting question on the C# in Depth Forum. It was sufficiently unexpected that we reckoned it was worth a note.

He had code which effectively looked like this (I've pared it down a little):

using System;

class Test
{
    public static implicit operator Test(int i)
    {
        Console.WriteLine("Converted from "+i);
        return new Test();
    }
    
    static void Main()
    {
        int a = 10;
        Test normalConversion = a;
        
        int? b = 20;
        Test unexpectedConversion = b;
        
        int? c = null;
        Test evenOdder = c;
    }
}

The first conversion (normalConversion = a) is entirely normal - int to Test, using the implicit conversion. This prints 10.

The second conversion (unexpectedConversion = b) is one which I didn't expect to be legal - after all, we have an int? rather than an int, and there's no implicit conversion from int? to int. However, it compiles and runs, and prints 20.

What would it print if we were to convert from a null value? Well, that's what the third conversion (evenOdder = c) checks. Again, it compiles and executes without an exception - but nothing gets printed. The result is a null reference.

It turns out that the C# compiler is handling this as a lifted conversion - so it checks whether or not the source has a value or not, and returns a null value if not. If there is a value, it performs the appropriate conversion.

This is a compiler bug (confirmed by Eric). It's not behaving as per the spec (which disallows it) but the behaviour is unlikely to change now as it would break existing code.