Another commonly asked question relates to the behavior of Nullable<T> when used as type parameter to instantiate a generic class. It might be surprising that comparing such a parameter to null gives always false as a result. As it turns out, this is not related to Nullable<T>, but it is a result of how generics are implemented. There is a tendency to think about generics in a manner very similar to C++ templates, this view is unfortunately not correct.

Generics have a runtime representation and, as such, the compiler needs to generate IL that works whatever type is used at runtime to instantiate the generic class. This means that the compiler cannot call user defined operators, because at compile time it has no knowledge about them. The compiler doesn’t know at that point which type will be used to instantiate the generic class in the future. It cannot know that it has to generate code to call these user-defined operators.

In the same vein the compiler doesn’t know that a generic class will be instantiated with a Nullable<T> parameter and so it cannot produce IL to lift operators (i.e. the equality operator ‘==’ ). The result is that when the programmer writes code like ‘t==null’, the compiler generates IL to call the ‘standard’ ‘==’ operator, which in the case of Nullable<T> returns false because t has the runtime type of struct.

A similar behavior is observable with the following code using strings:

string s1 = "Bob John";

string s2 = "Bob";

Console.WriteLine(Equals(s1, s2 + " John"));

static bool Equals<T>(T a, T b) where T:class {

return a == b;

}

This code would return false, because the ‘==’ operator for reference types gets invoked.

A case could be made that the compiler should generate IL to check the runtime type of the generic parameter and call the correct operator for at least some well-known types. This solution wouldn’t work for user defined operators as the compiler doesn’t know at compile time the set of types that could be used to instantiate a generic class. In general we don’t like to keep this sort of ‘lists of special things’ in the codebase, More importantly the solution would impose a somehow significant performance penalty at each use of the operator. This feeling of ‘hacking’ things and the significant performance problem convinced us not to implement this solution.