C# 2.0: Generics, default(T) and compare to null weirdness
<Added additional stuff after a discussion on internal CSharp user list>
I was going through the generics spec in C#2.0 and found something really weird.
default(t)
Consider the following code.
class MyGenClass<T>{ public void Method(ref T t) { t = null ; }}
This will not compile because because T may be a value type as well and there is no implicit conversion of null to a value-type. So to handle such situation where you want to reset a type in a generic class the default-value expression was instroduced.
class MyGenClass<T>{ public void Method(T t) { t = default(T); }}
If the type in default-value expression at run-time evaluates to be a reference type then it'll return null, if its a value-type then it will return the value-types default value (essentially new T() which boils down to re-setting all bits to 0).
null comparison weirdness
Peculiarly though, you are allowed to compare t with null.
class MyGenClass<T>{ public void Method(T t) { if (t == null ) Console.WriteLine("null"); // do some processing*/ }}
I got a bit confused here, as I always thought that you cannot compare value-types to null. Then I tried the following code.
int i = 5;if (i == null ) Console.WriteLine("null");
and it compiled fine, with a warning that i == null will always be false. But the weird thing is that it compiled and I just cannot figure out why does the compiler allow comparing a value type with null. However, I accepted it and thought I figured out that since value-type can be compared to null it explains why in the generic class I was able to compare t to null. Things got even weirder when I tried to put a value-type constraint on the generic class
class MyGenClass<T> where T: struct{ public void Method(T t) { if (t == null) Console.WriteLine("null"); // do some processing*/ }}
Compilation failed, with the statement that == operator cannot be applied to T. Since compiler cannot validate that == operator is overloaded on T it fails. But what I cannot figure out is that even in the case where there were no constrains the compiler in no way can validate the same thing, then why did it allow the comparison with null.
Time to send an email to internal CSharp alias to figureout whats going on....
I did finally send the email to CSharp usergroup and after some discussions this is what came out.
The comparison of the form int a = 5; if (a==null) was invalid in VS 2003, but this has changed with nullable types. The int gets converted to a nullable type and the lifted operator bool operator ==(int? x, int? y); is used (see 2.0 spec 24.3.1) .
This answers why null comparison works in value types like int. Why null comparison is allowed in generic classes has a more involved reasoning. First of all there is an explicit rule in the spec which goes as
There are special rules for determining when a reference type equality operator is applicable. For an equality-expression with operands of type A and B, […] if B is the null type, […] A is a type parameter and at runtime, the type parameter is a value type, the result of the comparison is false.
This rule makes the comparison valid. However, I was not convinced about the reasoning behind the rule. Special cases like this break the flow and intuition one develops in a language. Moreover, it the above rule is valid then I'd argue the following code should also be valid
class MyGenClass<T> where T: struct{ public void Method(T t) { if (t == null) Console.WriteLine("null"); // do some processing*/ }}
Here for the comparison t == null, T should have been auto-converted to its nullable counterpart T? and the comparison should have gone through (evaluated to false). I think the fact that a generic class with no constraint allows an expression and a generic class with additional constraint do not allow the same is un-intuitive.
Comments
Anonymous
December 08, 2005
Doesn't the value type get autoboxed to object, then compared with null?
Not sure why the generic version fails, in that case, though.Anonymous
December 08, 2005
even if its auto-boxes what purpose can it serve? I mean an int boxed will always be non-null....Anonymous
December 08, 2005
I ran into a similar problem in a piece of code recently. I had a indexer of type T where I wanted to check to see if the value provided in the setter was the default value (ex. null for reference types, 0 for ints, etc).
I wrote something along the lines of:
if (value == default(T)) {
// do something
}
The compiler does not accept either the == or != operators for comparisons between two values of type T. The solution in my case was to put a constraint for either IEquatable<T> or IComparable<T> to perform comparisons.Anonymous
December 08, 2005
And of course, this gem is nice and weird (I know it worked in 1.0, pretty sure it does in 2.0 as well):
int i = (int)(object)null;
I never really tested what i will be but it certainly is weird. Null to value type covnersion...Anonymous
December 08, 2005
It doesn't autobox the int, it converts it to int?. Here's the warning you get when you compare an int variable to null:
warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'int?'
If you explicitly cast i to int?, you'll get a different warning: the code inside the if statement is unreachable (since the condition is always false). The same IL is produced.Anonymous
December 08, 2005
After some digging around and emails to the CSharp devs, I have located the culprit as lifted operators due to nullable types. So
int i = 5
if (i == null) works due to this lifted operators.
This will surely be my next blog topic. However, I am still not able to get why
class MyGenClass<T>
{
public void Method(T t)
{
if (t == null)
Console.WriteLine("null");
}
}
is allowed....Anonymous
December 08, 2005
Or why it isn't allowed when you add the struct constraint. Seems like it should lift T to T? just like it lifts int to int?.
This is definitely an ugly corner of the language.Anonymous
December 10, 2005
Abhinaba, maybe I'm misunderstanding your question, but it seems like you would want to allow it to work so can do this...
MyGenClass<int?> x = new MyGenClass<int?>();
int? i = null;
x.Method(ref i);Anonymous
December 10, 2005
The comment has been removedAnonymous
December 10, 2005
I have updated the post with more findings.
To restate :- The whole issue is involving lifted operators and the int getting auto-converted to int?. This explains why the comparison with int works. But I absolutely agree with Jesse McGrew. The T should be auto-converted to T? so that even with struct constraint the generic class should compile.Anonymous
December 10, 2005
The comment has been removedAnonymous
December 10, 2005
The int to int? lifting is a speciall case as
given:
struct TestStruct { }
void Test {
TestStruct s
if(s == null) {
Console.WriteLine("null");
}
}
I get a compile error.Anonymous
March 09, 2007
I tried this and it workedT objdefault(T).Equals(obj);Anonymous
March 15, 2007
The Mono project just ran into this issue:http://tirania.org/blog/archive/2007/Mar-13-1.htmlAnonymous
March 29, 2007
default(T).Equals(obj);DOES NOT WORK if T is a reference type since you cannot call .Equals on null...Anonymous
July 09, 2007
I want to do something like:class myObject{public T Fetch<T>(ICriteria oCriteria){ T fetchResult = default(T); if(T == something){ fetchResult = doSomething(); } }}private something doSomething(){ return new something();}}which gives me a compile time error at fetchResult = doSomething();Any help?Regards,SunnyAnonymous
December 05, 2007
"The int gets converted to a nullable type" - nope, i checked the IL and it's not, still int32Anonymous
May 20, 2008
So If You'd Like To Check Inside A Generic Class If A Variable Eqauls To Default You Should Do: public bool IsDefault(TInterpretationResult res) { //Compiler Error //return (default(T) == res); var obj = default(T) as object; if (obj == null) //Is Not Value Type return (res == null); if (obj.GetType().IsValueType) { return (ValueType)obj == (ValueType)obj; } Debug.Fail("Should Not Reach Here!"); return false; } Seems Kinda Ugly, It's Strange That MS Didn't Handle It In A More Elegent WayAnonymous
May 20, 2008
Oops ... Fixed: public static bool IsDefaultValue(T res) { //Compiler Error //return (default(T) == res); var obj = default(T) as object; if (obj == null) //Is Not Value Type return (res == null); if (obj.GetType().IsValueType) { if (res == null) //res Is Not A Value Type return false; return res.Equals(obj); } Debug.Fail("Should Not Reach Here!"); return false; } Still Ugly!Anonymous
November 12, 2008
// The clean way ;) public static bool IsDefaultValue(T res) { return Object.Equals(res, null); }Anonymous
February 04, 2009
Thanks for the info! I just ran into this exact problem.Anonymous
December 22, 2009
If a Generic Type T is implementing an interface, it would therefore assume it was a reference type, and thus nullable? isn't this correct. i.e. public T x<T>() where T : IMyInterface { return null; }Anonymous
January 02, 2011
to avoid comparing to "null", you might use EqualityComparer<T>.Default.Equals(T x, default(T))Anonymous
October 16, 2011
for those who might stumble upon this through search, this might help: here is a solution: www.geekality.net/.../generics-and-checking-for-null