Welcome to MSDN Blogs Sign in | Join | Help

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.

 

Published Sunday, December 11, 2005 12:44 AM by abhinaba
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 08, 2005 9:34 AM by David McCabe
Doesn't the value type get autoboxed to object, then compared with null?

Not sure why the generic version fails, in that case, though.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 08, 2005 10:45 AM by abhinaba
even if its auto-boxes what purpose can it serve? I mean an int boxed will always be non-null....

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 08, 2005 12:53 PM by James Arendt
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.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 08, 2005 1:55 PM by SOS
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...

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 08, 2005 7:52 PM by Jesse McGrew
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.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Friday, December 09, 2005 6:38 AM by abhinaba
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....

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Friday, December 09, 2005 7:17 AM by Jesse McGrew
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.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Saturday, December 10, 2005 1:24 PM by Sean Chase
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);

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Saturday, December 10, 2005 1:55 PM by Sean Chase
Here's another weird snippet that compiles...

public struct Foo { public int Bar;}

public struct Nullable<Foo> {}


What's the point of allowing the latter struct declaration?

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Saturday, December 10, 2005 2:51 PM by abhinaba
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.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Saturday, December 10, 2005 2:55 PM by TAG
Sean,

Becouse Nullable<Foo> is same as Nullable<T>. Foo in this case is simply typename.

Nullable in this case is new generic struct type declaration in your namespace.

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Saturday, December 10, 2005 4:42 PM by Tobias
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.

# http://blogs.msdn.com/brada/category/2556.aspx

Sunday, February 05, 2006 3:03 PM by TrackBack

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Friday, March 09, 2007 10:19 AM by twvaldez

I tried this and it worked

T obj

default(T).Equals(obj);

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, March 15, 2007 8:37 PM by Name required

The Mono project just ran into this issue:

http://tirania.org/blog/archive/2007/Mar-13-1.html

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, March 29, 2007 10:46 AM by Jochen

default(T).Equals(obj);

DOES NOT WORK if T is a reference type since you cannot call .Equals on null...

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Monday, July 09, 2007 12:08 PM by Sunny

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,

Sunny

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Thursday, December 06, 2007 12:54 AM by jokiz

"The int gets converted to a nullable type" - nope, i checked the IL and it's not, still int32

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Tuesday, May 20, 2008 5:19 PM by Kc Joe

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 Way

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Tuesday, May 20, 2008 5:25 PM by Kc Joe

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!

# re: C# 2.0: Generics, default(T) and compare to null weirdness

Wednesday, November 12, 2008 11:39 AM by Jonathan Delorme

// The clean way ;)

public static bool IsDefaultValue(T res)

{

return Object.Equals(res, null);

}

Leave a Comment

(required) 
required 
(required) 
 
Page view tracker