Jaa


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 removed

  • Anonymous
    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 removed

  • Anonymous
    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.html

  • Anonymous
    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,Sunny

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

  • Anonymous
    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 Way

  • Anonymous
    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