April 25th, 2006
One of the “late-breaking features” of C# 2.0 was nullable types. They are supposed to make object-relational mapping easier, being effectively nullable primitives which behave more closely like the types in SQL. However I have just spent an hour debugging a problem which demonstrates very clearly how language features such as this simply make life more difficult for developers.
In .NET, as in most object oriented languages, there is a distinction between primitive types and objects. In C#, as in C++, it is possible to define your own primitives using the struct keyword. C# structs cannot be inherited from, cannot have null assigned to them, and have their memory allocated from the stack instead of the heap.
The fact that structs are not nullable is a frequent irritation when coding in C#. For example, the .NET DateTime type is defined as a struct, and so when designing a form which has an optional date field, much messing around results. Your options are basically to create an object which wraps the DateTime struct, or to make DateTime.MinValue behave like null. Even though the latter is clearly evil and dirty, for some reason we have adopted it in our code.
With the advent of generics in C# 2.0, a solution was devised for this problem. First of all, the .NET framework provides a struct called Nullable<T>, which behaves like a wrapper for primitives. Then they added some syntactical sugar such that when you append ? to a primitive declaration, it compiles into an object of type Nullable<primitive>. So for example decimal? needlesInMyEye becomes Nullable<decimal> needlesInMyEye.
However this becomes a pain in the ass exactly where you most want it – when doing object relational mapping. So in my current project we’re binding a field in our form to a property of one of our domain objects. The property in question is mapped to a NOT NULL field in the relevant table, and so in the class definition it is defined as decimal?. Weirdly though, the binding fails. When you enter the string “12.5″ in the form field, the property gets assigned “null”. If you remove the ?, the binding works and the property gets assigned the value 12.5.
Once you know how the syntactical sugar works and hence what is happening under the hood, it is obvious why this occurs. The form field binding knows how to cast a string to a decimal, but not how to cast a string to an object of type Nullable<decimal>, and so assigns null to it instead. So we’ve introduced a temporary hack to by providing a wrapper for the property which takes type string and does the parsing explicitly. Great.
More generally, it is tempting for developers to mix nullable and non-nullable types in the domain, believing they are basically the same type of thing when in fact they are fundamentally different. You then end up in a situation where you don’t know if you’re dealing with a generic struct or a primitive. Since these types are not interchangeable, you are just going to end up with a series of hacks throughout the codebase like the one described in the paragraph above.
I think the root problem is having a distinction between primitives and objects in the first place. In languages like Smalltalk and Ruby there is no such distinction – everything is an object. Clearly the fact that these languages are neither strongly nor statically typed removes the problem of casting and makes it easier to avoid primitives. But I would love to have people give me some good reasons why C# and Java require primitives (over and above arguments based on speed and efficiency).
Meanwhile the advice is clear: don’t mix and match nullable types and primitives; they won’t autobox and they aren’t interchangeable. To avoid confusion, prefer objects to nullable types – in almost all cases you’ll have behaviour you want to give them, in which case you’ll want objects in any case. In particular, having static methods that operate on nullable types is a clear warning sign that what you actually want is an object.
Update: binding to nullable types in WinForms
Originally in this update I provided a complicated solution to the problem mentioned above that binding to nullable types didn’t appear to work. It turns out that it in fact does work, provided you explicitly set the DataSourceUpdateMode to DataSourceUpdateMode.OnPropertyChanged (new in .NET 2.0) when doing the binding. Thanks to Edbert for the tip (see comment below).