Understanding C#: Nullable Types

By Andrew Stellman
November 7, 2010 | Comments: 3

Every C# developer knows how to work with value types like int, double, boolean, char, and DateTime. They're really useful, but they have one flaw: they can't be set to null. Luckily, C# and .NET give you a very useful tool to for this: nullable types. You can use a nullable type any place that you need a variable that can either have a value or be null. This seems like a simple thing, but it turns out to be a highly flexible tool that can help make your programs more robust.

In this tutorial, which I've adapted from my book, Head First C#, I'll show you the basics of nullable types, and give you a quick example of a program that uses them to handle unpredictable user input.

Head First C# Cover

Use nullable types when you need nonexistent values

If you're a Head First C# reader, take a minute and flip back to the contact cards you converted to a database way back in the Contacts project in Chapter 1. (If you're not a Head First C# reader, that chapter is a tutorial that walks you through creating a simple database-driven application for managing contacts. You download the first three full chapters—here's a link to the free Head First C# eBook PDF download.) In the Contacts project project, you set up your table to allow nulls for each of its columns. That way, if someone left out a value or wrote something illegible on their contact card, the database could use null to represent that it doesn't have a value.

Did it seem odd that even the Client column was set to allow nulls? Someone's either a client or not, right? But there was no guarantee that every card has the Client blank filled in, and the database needed a way to represent that we might not know if someone's a client or not.

Now, in a database you could just use null. But value types like ints and Booleans can't be set to null. If you added these statements to your program:

  bool myBool = null;
  DateTime myDate = null;

you'd get a compiler error.

But sometimes you need to set a number, boolean, or DateTime value to null. Let's say your program needs to work with a date and time value. Normally you'd use a DateTime variable. But what if that variable doesn't always have a value? That's where nullable types comes in really handy. All you need to do is add a question mark (?) to the end of any value type, and it becomes a nullable type that you can set to null.

That means these statements will compile just fine:

  bool? myNulableInt = null;
  DateTime? myNullableDate = null;

Every nullable type has a property called Value that gets or sets the value. A DateTime? will have a Value of type DateTime, an int? will have one of type int, etc. They'll also have a property called HasValue that returns true if it's not null.

You can always convert a value type to a nullable type:

  DateTime myDate = DateTime.Now;
  DateTime? myNullableDate = myDate;

But you need to cast the nullable type in order to assign it back to a value type:

  myDate = (DateTime) myNullableDate;

But you also get this handy Value property—it also returns the value:


  myDate = myNullableDate.Value;

If HasValue is false, the Value property will throw an InvalidOperationException, and so will the cast (because that cast is equivalent to using the Value property).

The question mark T? is an alias for Nullable<T>

When you add a question mark to any value type (like int? or decimal?), the compiler translates that to the Nullable<T> struct (Nullable<int> or Nullable<decimal>). You can see this for yourself: add a Nullable<DateTime> variable to a program, put a breakpoint on it, and add a watch for it in the debugger. You'll see System.DateTime? displayed in the watch window in the IDE. This is an example of an alias, and it's not the first one you've encountered. Hover your cursor over any int. You'll see that it translates to a struct called System.Int32:

Screenshot - int alias struct System.Int32.png

You can use the IDE's IntelliSense to explore the members of that struct. You'll see that int.Parse() and int.TryParse() are members.

Take a minute and do that for other value types: boolean, char, double, decimal, etc. Notice how all of them are aliases for structs. (Now do the same thing string. It's not a struct, but rather a class called System.String. That makes sense! String is a reference type, not a value type, and this is a helpful way for a novice developer to remember that.)

When you use Nullable types, not only do you get a type that can either have a value or be null, but you also get some useful methods that can come in handy when you're checking for a value. Nullable<T> is a struct that lets you store a value type OR a null value.

Here's a partial class diagram with a few of the methods and properties on Nullable<DateTime>:

Nullable DateTime class diagram.png

Nullable types help you make your programs more robust

Users do all sorts of crazy things. You think you know how people will use a program you're writing, but then someone clicks buttons in an unexpected order, or enters 256 spaces in a text box, or uses the Windows Task Manager to quit your program halfway through writing data to a file, and suddenly it's popping up all manner of errors. A program that can gracefully handle badly formatted, unexpected, or just plain bizarre input is called robust. (That's another thing we talked about in Head First C#!) Well, when you're processing raw input from your users, nullable types can be very useful in making your programs more robust.

Now see for yourself—create a new console application in Visual Studio and add this RobustGuy class to it:

Try typing in the method declarations instead of just pasting in all the code. When you add the RobustGuy.ToString() override, take a look at the IntelliSense window when you enter Birthday.Value. Since the Value property is a DateTime, you'l l see all the usual DateTime members. Use the ToLongDateString() method to convert it to a human-readable string.

/// <summary>
/// The RobustGuy class stores a guy's height and birthday using nullable types
/// </summary>
class RobustGuy
{
     /// <summary>
     /// The Birthday property is a Nullable<DateTime>. If the birthday is not known,
     /// it has no value.
     /// </summary> 
     public DateTime? Birthday { get; private set; }
 
     /// <summary>
     /// The Height property is a Nullable<int> that holds the height (if known)
     public int? Height { get; private set; }
 
 
     /// <summary>
     /// <param name="birthday">String containing user input for a birthday</param>
     /// <param name="height">String containing user input for a height in inches</param>
     /// </summary>
     public RobustGuy(string birthday, string height)
     {
          // The constructor uses the DateTime and int.TryParse() methods to attempt to
          // convert the user input into values.
  
          DateTime tempDate;
          if (DateTime.TryParse(birthday, out tempDate))
              Birthday = tempDate;
          else
              Birthday = null;
  
          int tempInt;
          if (int.TryParse(height, out tempInt))
              Height = tempInt;
          else
              Height = null;
      }
 
     public override string ToString()
     {
          // If the user entered garbage, the Nullable types won't have values, 
          // so their HasValue() methods will return false.
          //
          // Try experimenting with the other DateTime methods that start with "To" 
          // to see how they affect your program's output.
  
          string description;
          if (Birthday != null)
              description = "I was born on " + Birthday.Value.ToLongDateString();
          else
              description = "I don't know my birthday";
          if (Height != null)
              description += ", and I'm " + Height + " inches tall";
          else
              description += ", and I don't know my height";
          return description;
      }
}

And here's the Main( ) method for the program. It uses Console.ReadLine() to get input from the user:

static void Main(string[] args)
{
     Console.Write("Enter birthday: ");
     string birthday = Console.ReadLine();
     Console.Write("Enter height in inches: ");
     string height = Console.ReadLine();
     RobustGuy guy = new RobustGuy(birthday, height);
     Console.WriteLine(guy.ToString());
     Console.ReadKey();
}

Here's what it looks like when you run the program and give it some values:

Screenshot - RobustGuy nullable types demo.png

When you run the program, see what happens when you enter different values for dates. DateTime.TryParse() can figure out a lot of them. When you enter a date it can't parse, the RobustGuy's Birthday property will have no value.

Andrew Stellman is the author of Head First C# and other books from O'Reilly. You can read more from Andrew at Building Better Software.


You might also be interested in:

3 Comments

Thanks for sharing. Very informative.

I don't get this line

myDate = ( DateTime) myNullableDate;


Why not just use:

myDate = myNullableDate.Value;

It does the same job (and throws the same exception) as your cast method.

Yes, you're right -- I wanted to show casting first (because I thought that helped make it a little clearer), and then use the .Value property later on in the code example. I guess it made things less clear, not more! :)

I just went back and added in an extra line demonstrating the Value property. It does look better this way.

Thanks for pointing it out!

News Topics

Recommended for You

Got a Question?