Why can I test a generic for null when it may not be nullable or may not be an object?
The question is ill-posed; the value of any expression of generic type will always be either an object or a null reference at runtime. I think you meant to ask:
Why can I test a generic for null when it its runtime type might be neither a nullable value type nor a reference type?
The C# specification guarantees that equality comparison against the literal null is legal in section 7.6.10, which I quote here for your convenience:
If an operand of a type parameter type T is compared to null, and the run-time type of T is a value type, the result of the comparison is false. [...] The x == null
construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type.
Note that there is a small error in the spec here; the last sentence should end "non-nullable value type."
I'm never sure if I've actually answered a "why?" question satisfactorily or not. If that's not satisfactory, try asking a more specific question.
Shouldn't the compiler warn me that I use null?
No. Why do you believe that the compiler should warn you for using a feature of the language correctly?
is it using boxing conversion to an object to do a test for null here?
Well, that's a bit of a tricky point. On the one hand, section 7.6.10 states:
The predefined reference type equality operators never cause boxing operations to occur for their operands. It would be meaningless to perform such boxing operations, since references to the newly allocated boxed instances would necessarily differ from all other references.
However, when we generate IL for the comparison of a generic T to null, we of course actually do generate a box of T, a load of null, and a comparison. The jitter can be smart enough to elide the actual memory allocation of the boxing; if T is a reference type then it needs no boxing, if it is a nullable value type then it could be turned into a call to check the HasValue property, and if it is a non-nullable value type then the boxing and check can be turned into simply generating "false". I do not know exactly what the various different jit compiler implementations actually do; if you're interested, check and see!