Search code examples
c#referencenullvalue-type

Does a generic function implicitly cast value types to objects when checking for null?


For example the following code demonstrates my line of thought:

class Program
{
    static void Main(string[] args)
    {
        int i = 0;
        IsNull(i);  // Works fine

        string s = null;
        IsNull(s);  // Blows up
    }

    static void IsNull<T>(T obj)
    {
        if (obj == null)
            throw new NullReferenceException();
    }

}

Also the following code:

int i = 0;
bool b = i == null;  // Always false

Is there an implicit object cast going on? such that:

int i = 0;
bool b = (object)i == null;

Solution

  • Yes, obj gets boxed by the compiler. This is the IL generated for your IsNull function:

    .maxstack 8
    
    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: brtrue.s IL_000e
    
    IL_0008: newobj instance void [mscorlib]System.NullReferenceException::.ctor()
    IL_000d: throw
    
    IL_000e: ret
    

    The box instruction is where the casting happens.

    The compiler doesn't know anything specific about T so it must assume that it must be an object - the base type of everything in .NET; this is why it boxes obj to make sure that the null check can be performed. If you use a type constraint you can give more information to the compiler about T.

    For example, if you use where T : struct your IsNull function won't compile anymore because the compiler knows T is a value type and null is not a value for value types.

    Boxing a value type instance always returns a valid (non-null) object instance*, so the IsNull function would never throw for a value type. This is actually correct behavior if you think about it: the numeric value 0 is not null - a value type value cannot possibly be null.

    In the code above brtrue.s is very much like if(objref!=0) - it doesn't check the value of the object (the value type value before boxing) because at the time of the check, it's not a value that's on top of the stack: it's the boxed object instance that's on top. Since that value (it's really a pointer) is non-null, the check for null never comes back as true.

    *Jon Hanna pointed out in a comment that this statement is not true for default(Nullable<T>) which is correct - boxing this value returns null for any T.