Why Won't using Throw a NullReferenceException

Today someone was curious why C#'s using statement won't throw a NullReferenceException. They had a using statement that opened a registry key, but even if that key didn't exist and the return value was null, they didn't have to worry about a NullReferenceException being thrown.

Before we start to look at why the exception isn't thrown, its useful to think about why one might be thrown in the first place. Using is syntactic sugar for wrapping a call to Dispose() in a try ... finally block. Most articles about IDisposable will show that the code:

using(DisposableObj x = new DisposableObj())
    x.DoSomething();

is essentially equivalent to

DisposableObj x = new DisposableObj();
try
{
    x.DoSomething();
}
finally
{
    x.Dispose();
}

Lets look at a more real life code snippet. The program in question involved opening a registry key, and not getting a NullReferenceException when the key didn't exist. The following code sample prints "Got a null key" to the console, but does not throw.

using System;
using Microsoft.Win32;

public class UsingNull
{
    public static void Main()
    {
        using(RegistryKey key = Registry.CurrentUser.OpenSubKey(@"SomeKey\WhichIsntThere"))
        {
            if(key == null)
                Console.WriteLine("Got a null key");
        }
    }
}

If using really works as I said above, why doesn't this cause a NullReferenceException when its time to Dispose the key? There are a few possibilities.

  1. Finally blocks don't allow exceptions to escape
  2. There's some sort of IDisposable magic, which disregards calls on null objects
  3. The key isn't actually null at all, but is in a special "null" state, and has overloaded the == operator, and returns true when compared against null
  4. The C# compiler actually generates some additional code when compiling a using statement

Options 1 and 2 are clearly incorrect, so that leaves us with two choices. Either the people who created the RegistryKey class are very clever, and created a non-null registry key object that's overloaded to act like it's null in comparisons, or the C# compiler is doing some extra work. Opening up the RegistryKey class in ildasm quickly shows that there are no overloaded operators defined, so that leaves us only with option number 4.

To verify this, lets crack open the code that the C# compiler produces from the snippet above. Running it through ildasm yields:

.locals init (class [mscorlib]Microsoft.Win32.RegistryKey V_0)
IL_0000:  ldsfld       class [mscorlib]Microsoft.Win32.RegistryKey [mscorlib]Microsoft.Win32.Registry::CurrentUser
IL_0005:  ldstr        "SomeKey\\WhichIsntThere"
IL_000a:  callvirt     instance class [mscorlib]Microsoft.Win32.RegistryKey [mscorlib]Microsoft.Win32.RegistryKey::OpenSubKey(string)
IL_000f:  stloc.0
.try
{
  IL_0010:  ldloc.0
  IL_0011:  brtrue.s   IL_001d
  IL_0013:  ldstr      "Got a null key"
  IL_0018:  call       void [mscorlib]System.Console::WriteLine(string)
  IL_001d:  leave.s    IL_0029
}  // end .try
finally
{
  IL_001f:  ldloc.0
  IL_0020:  brfalse.s  IL_0028
  IL_0022:  ldloc.0
  IL_0023:  callvirt instance void [mscorlib]System.IDisposable::Dispose()
  IL_0028:  endfinally
}  // end handler
IL_0029:  ret

Lets take a closer look. Lines IL_000 through IL_00f are the call to RegistryKey.OpenSubKey(). The result is stored in the first local variable. From IL_0010 to IL_001d is the try block that we expect to see, and the finally block starts at line IL_001f. If this was a straight call to IDisposable, I'd expect to see only a ldloc followed by a callvirt instruction. However, there are a few other instructions present. Especially interesting are lines IL_001f and IL_0020. What these lines are doing is checking to see if the first local variable (in this case our registry key) is null, and if so skipping to the end of the finally block. From looking at this disassembly, it turns out that the code the C# compiler generates more closely resembles:

DisposableObj x = new DisposableObj();
try
{
    x.DoSomething();
}
finally
{
    if(x != null)
        x.Dispose();
}

What does this mean to you? Probably not much of anything (aside from a possibly interesting look at how the C# compiler generates code for the using statement). As long as your code doesn't rely on the value being used inside the using statement to be non-null, and also doesn't rely on using throwing a NullReferenceException if this value is null, you're code is fine. However, if either one of those conditions is true, you'd be better off fixing the offending code before you have a subtle bug to trace down later.