Friday, 22 January 2010

Testing for default T - Generics

I came accross this SO post after discovering you cannot just write:


if ( T == default(T) ) {}


You actually need either
if ( object.Equals(value, default(T)) ) {}


or (according to the SO post more efficient - though I've not checked this)
if (EqualityComparer.Default.Equals(value,default(T)))  {}



UPDATE:

I was asked the question: “I assume that this is to avoid evaluating the reference (pointer) rather than the value?”

I think that would depend on the implementation of the compare method for the type being used and as the SO post suggests this isn’t the whole story… to take a step back…

The reason for this roundabout way of checking that the value (T) is empty is logically because T could be either value or reference type, so a method is needed which can be made to deal with both value and reference types.

As you suggest Object.Equals() is a compares pointers (references), so if T was a value type it would first need boxing (first needs to be turned into a reference type), you can see this in the IL (example1 below – a contrived and nonsense function!) …

Also note in the IL that “default(T)” statement means that T is boxed too – this will be because T can be either reference or value type, so the IL boxes to force the issue (actually this surprises me a bit, I was thinking the compiler would be able to work this out based on the use, but maybe that’s asking a lot?!).

So to go back to the original post (and a check in MSDN confirms this) the use of “EqualityComparer.Default.Equals(value, default(T))” – because the underlying implementation supports value and reference types [class/struct] - means that the IL generated no longer needs to first box (example2) and I believe that’s why it’s considered more efficient and that’s the jist of the comment in SO.

Note: I haven’t dug down any deeper, it could well be that if the code falls back to Object.Equals for a value type then boxing will still occur.


Examples:

        public static T Foo()
        {
            T r = default(T);
            int x=0;
            string y = "";
            bool result = object.Equals(x, default(T));
            return r;
        } 

(abridged)
IL_0012: box [mscorlib]System.Int32
IL_0017: ldloca.s CS$0$0001
IL_0019: initobj !!T
IL_001f: ldloc.s CS$0$0001
IL_0021: box !!T
IL_0026: call bool [mscorlib]System.Object::Equals(object, object)

        public static T Foo()
        {
            T r = default(T);
            int x=0;
            string y = "";
            bool b = EqualityComparer.Default.Equals(r, default(T));
            return r;
        }


(abridged)
IL_0001: ldloca.s r
IL_0003: initobj !!T
.
.
IL_0019: initobj !!T
IL_001f: ldloc.s CS$0$0001
IL_0021: callvirt instance bool class [mscorlib]System.Collections.Generic.EqualityComparer`1::Equals(!0, !0)

1 comment:

Yossi Dahan said...

..and this is where I get quite annoyed with .net as the first option is so much more readable than the last!