Detecting Default Values of Value Types

Func<object, bool> func = (obj) => obj.Equals(default(Int32));
Func<object, bool> func = (obj) => obj.Equals(default(Int32));
Introduction

I don’t know if this happened to you: the need to find out if some instance of a class is the class’ default value. For reference types – which include nullables -, it is always null, and for value types, it is the value that you get if you do not initialize a field of that type or if you call its default parameterless constructor – false for Boolean, 0 for numbers, the 0 member of an enumeration, etc. So, the problem is, how can we tell if some instance represents this default value, dynamically, that is, for any value type, not just a specific one.

Again, nothing special, just the result of a lazy Saturday afternoon, still, hope you see some value in it! Winking smile

Take 0: Direct Comparison

How would we compare a built-in type instance, like int, with its default value? Just compare it with 0. This is useful to see how each of the other techniques compare to it.

static bool IsDefaultDirect(int obj)
{
    return obj.Equals(0);
}

Take 1: Comparison With a New Instance

An easy way is to compare the value we have with a new instance of the same type, like this:

static bool IsDefaultUsingConstruction(ValueType obj)
{
    return obj.Equals(Activator.CreateInstance(obj.GetType()));
}

Activator.CreateInstance knows how to create a default instance of a value type, so we’re good.

Take 2: Using Generics Directly

Another option is to use the ability to compare a generic variable with the default value for the generic type. This cannot be used in exactly the same way as #1, because here we need to explicitly call the comparison method with a generic parameter:

static bool IsDefaultUsingGeneric<T>(T obj) where T : struct
{
    return obj.Equals(default(T));
}

Notice the struct constraint, it is exactly the same as declaring the parameter as ValueType, because it is the base class for all value types.

Take 3: Using Generics Dynamically

You can make the previous example dynamic, with the cost of an additional method invocation, like this:

static bool IsDefaultUsingReflection(ValueType obj)
{
    //cache this, please
    var isDefaultUsingGenericMethod = typeof(Program).GetMethod("IsDefaultUsingGeneric", BindingFlags.Static | BindingFlags.NonPublic);
    var method = isDefaultUsingGenericMethod.MakeGenericMethod(obj.GetType());
    return (bool) method.Invoke(null, new object[] { obj });
}

Take 4: Using a LINQ Expression Bound to a Specific Type

Another option is to dynamically compile a LINQ expression that performs the comparison, something like this:

Func<T, bool> func = (obj) => obj.Equals(default(T));

We can create this expression dynamically, and bind it to the desired value type:

static bool IsDefaultUsingLinq(ValueType obj)
{
    var argType = obj.GetType();
    var arguments = new Expression[] { Expression.Default(argType) };
    var paramExpression = Expression.Parameter(argType, "x");
 
    var equalsMethod = argType.GetMethod("Equals", new Type[] { argType });
    var call = Expression.Call(paramExpression, equalsMethod, arguments);
 
    var lambdaArgType = typeof(Func<,>).MakeGenericType(argType, typeof(bool));
    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);
 
    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as LambdaExpression;
 
    //cache this, please
    Delegate func = expression.Compile();
 
    return (bool)func.DynamicInvoke(obj);
}

Take 5: Using a LINQ Expression Bound to Object

A very similar option to #4 is to use Object.Equals instead of the value type’s specific Equals method, like this:

Func<object, bool> func = (obj) => obj.Equals(default(int));

Of course, the int parameter depends on the actual type of the value type parameter being passed:

static readonly MethodInfo LambdaMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == "Lambda" && x.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));
static readonly MethodInfo EqualsMethod = typeof (object).GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public);
 
static bool IsDefaultUsingLinqObject(ValueType obj)
{
    var argType = typeof(object);
    var arguments = new Expression[] { Expression.Convert(Expression.Default(obj.GetType()), argType) };
    var equalsMethod = EqualsMethod;
    var paramExpression = Expression.Parameter(argType, "x");
    var call = Expression.Call(paramExpression, equalsMethod, arguments);
    var lambdaArgType = typeof(Func<object, bool>);
    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);
    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as Expression<Func<object, bool>>;
 
    //cache this, please
    Func<object, bool> func = expression.Compile();
 
    return func(obj);
}

Because the comparison expression, of type Func<object, bool>, is strongly typed, we avoid the need to call Delegate.DynamicInvoke, the performance increases substantially.

Take 6: Using Formatter Services

A long, long time ago, in a distance galaxy, I already mentioned, en passant, the usage of FormatterServices.GetUninitializedObject to create instances of a type. Picking up example #1, let’s replace Activator.CreateInstance by FormatterServices.GetUninitializedObject and see the gains:

static bool IsDefaultUsingFormatterServices(ValueType obj)
{
    return obj.Equals(FormatterServices.GetUninitializedObject(obj.GetType()));
}

Take 7: Using a LINQ Expression Bound to a Specific Type and Using Invocation Through Dynamics

What a long name… Smile Well, this one is identical to #4, but without Delegate.DynamicInvoke. Instead, I make use of the dynamic type’s late binding to invoke the delegate, which results in even better performance:

static readonly MethodInfo LambdaMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == "Lambda" && x.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));
 
static bool IsDefaultUsingLinqAndDynamic(ValueType obj)
{
    var argType = obj.GetType();
    var arguments = new Expression[] { Expression.Default(argType) };
    var paramExpression = Expression.Parameter(argType, "x");
    var equalsMethod = argType.GetMethod("Equals", new Type[] { argType });
    var call = Expression.Call(paramExpression, equalsMethod, arguments);
    var lambdaArgType = typeof(Func<,>).MakeGenericType(argType, typeof(bool));
    var lambdaMethod = LambdaMethod.MakeGenericMethod(lambdaArgType);
    var expression = lambdaMethod.Invoke(null, new object[] { call, new ParameterExpression[] { paramExpression } }) as LambdaExpression;
 
    //cache this, please
    Delegate func = expression.Compile();
 
    dynamic arg = obj;
    dynamic del = func;
 
    return del(arg);
}

Measuring

I put in two methods for measuring calls and doing averages:

static long MeasureTicks(Action action)
{
    var watch = Stopwatch.StartNew();
 
    action();
 
    return watch.ElapsedTicks;
}
 
static float Measure(int times, Action action)
{
    var avg = 0L;
 
    for (var i = 0; i < times; ++i)
    {
        avg += MeasureTicks(action);
    }
 
    return (float)avg / times;
}

I used a Stopwatch to obtain the ElapsedTicks of the method to be exercised. I changed the methods I presented, namely, #4, #5 and #7, so as to cache the types and delegates created dynamically, this is crucial, and I leave that as an exercise to you – just remember that each method can potencially be called with different values, of different types. Then I added a warm-up step, which exercises the code using an integer parameter:

static void Warmup(int value)
{
    var times = 1;
    Measure(times, () => IsDefaultDirect(value));
    Measure(times, () => IsDefaultUsingConstruction(value));
    Measure(times, () => IsDefaultUsingGeneric(value));
    Measure(times, () => IsDefaultUsingReflection(value));
    Measure(times, () => IsDefaultUsingLinq(value));
    Measure(times, () => IsDefaultUsingLinqObject(value));
    Measure(times, () => IsDefaultUsingFormatterServices(value));
    Measure(times, () => IsDefaultUsingLinqAndDynamic(value));
}

In the past, I learned that a warm-up method – or lack of it – makes a huge difference.

I executed each option 100 times and got its results:

static void Measure()
{
    var times = 100;
    var value = 100;
 
    Warmup(value);
 
    var m0 = Measure(times, () => IsDefaultDirect(value));
    var m1 = Measure(times, () => IsDefaultUsingConstruction(value));
    var m2 = Measure(times, () => IsDefaultUsingGeneric(value));
    var m3 = Measure(times, () => IsDefaultUsingReflection(value));
    var m4 = Measure(times, () => IsDefaultUsingLinq(value));
    var m5 = Measure(times, () => IsDefaultUsingLinqObject(value));
    var m6 = Measure(times, () => IsDefaultUsingFormatterServices(value));
    var m7 = Measure(times, () => IsDefaultUsingLinqAndDynamic(value));
}

The results I got were:

MethodTicks Difference
#0: Direct Comparison 1.82131.88%
#1: Comparison With a New Instance1.92139.13%
#2: Using Generics Directly1.46105.80%
#3: Using Generics Dynamically6.9500%
#4: Using a LINQ Expression Bound to a Specific Type3.05221.01%
#5: Using a LINQ Expression Bound to Object1.61116.67%
#6: Using Formatter Services1.53
#7: Using a LINQ Expression Bound to a Specific Type and Using Invocation Through Dynamics1.38100%


Conclusion

I was really surprised that the direct comparison is actually – at least for integers – not the best way to see if a value is the default for its type! There’s a big range in results, and I can say that I was expecting that for #3. I knew that FormatterServices.GetUninitializedObject would give better results than Activator.CreateInstance, but I imagine this cannot be used with all types, because it doesn’t run the type’s constructor, possibly skipping some default initializations. I also knew that the performance of Delegate.DynamicInvoke is less than ideal, but it was interesting to see that dynamics can improve it.

As always, I’d love to see what you have to say! Do you see flaws in my approach, or do you know of any better solutions? Fire away! Winking smile






                             

10 Comments

  • This is a great post! Thank you for being lazy on a Saturday and posting it. :D This definitely gets a tweet.

  • Hi, Mike!
    Thanks! Good to see laziness appreciated, for a change! ;-)

  • So I am actually trying to integrate this with a framework that I am working with and currently building (read: extremely proof-of-concept), you can see this here:
    https://github.com/DevelopersWin/VoteReporter/blob/97d233f012eeceed8d072ebf6321989b64cf473a/Framework/DragonSpark/TypeSystem/TypeAdapter.cs#L27

    I am using the code for #7 above, but it appears to be referencing a "LambdaMethod" variable that I cannot seem to find on this page. Is this referenced anywhere? Or am I missing something really obvious (likely).

    Thank you,
    Mcihael

  • Ooops, sorry!! Yes, both the LambdaMethod (#5 and #6) and EqualsMethod (#5) fields were missing, thanks for pointing that out!

  • Sure thing! I got that working... looks good. However, in my case I do not have an instance available to me, just the Type/TypeInfo. Is it possible to use #7 with just the Type/TypeInfo and not an instance object?

    See... the reward for hard work is more hard work. ;)

  • Mike,
    I don't follow you! The purpose is to find out if *an instance* is identical to the type's default value, when the type is a value object! You can indeed just build the expression and compile it, just set argType to whatever type you want.

  • I'm with you! In my case I am looking to find the default value of a particular type, if that makes sense. This is still great information, nonetheless!

  • 1. Can you please show the full code, as I get different results.
    2. You do not use the result of the methods, so the jit compiler can just remove the method calls.
    3. IsDefaultUsingGeneric has an boxing conversion, to remove it you need to use IEquatable<T> type constraint.
    4. Other methods also have unnecessary boxing.
    5. You are creating unnecessary generalization here.

  • Hi, Alex!

    1 - Well, you're doing something different, that's all I can say; my results are consistent, even today I ran them again, the only thing I have is cache:

    static readonly MethodInfo IsDefaultUsingGenericMethod = typeof(Program).GetMethod("IsDefaultUsingGeneric", BindingFlags.Static | BindingFlags.NonPublic);
    static readonly MethodInfo LambdaMethod = typeof(Expression).GetMethods(BindingFlags.Static | BindingFlags.Public).First(x => x.Name == "Lambda" && x.GetParameters()[1].ParameterType == typeof(ParameterExpression[]));
    static readonly MethodInfo EqualsMethod = typeof (object).GetMethod("Equals", BindingFlags.Instance | BindingFlags.Public);
    static Func<object, bool> funcObject;
    static Delegate func;

    etc.
    2 - No, it can't. I *am* using the results of the methods; plus, I am running in Debug, which doesn't do that kind of optimizations;
    3 - You are right, but it makes absolute no difference:

    static bool IsDefaultUsingGeneric<T>(T obj) where T : struct, IEquatable<T>
    {
    return ((IEquatable<T>)obj).Equals(default(T));
    }

    gives the same results, in comparison (same overall position).
    4 - ? Please elaborate!
    5 - That's the whole point: I am trying to generalize, and I cannot expect that all structures implement IEquatable<T>! :-)

    Your last post was more than a year ago! Looking forward to seeing your implementations there, soon! ;-)

  • BTW, using a different structure:

    public struct Data : IEquatable<Data>
    {
    public Data(int a, string b)
    {
    this.A = a;
    this.B = b;
    }

    public int A { get; private set; }
    public string B { get; private set; }

    public bool Equals(Data other)
    {
    return this.A == other.A && this.B == other.B;
    }

    public override bool Equals(object obj)
    {
    return this.Equals((Data)obj);
    }
    }

    The order is almost the same:

    #7 - 1.99
    #2 - 2.0
    #0 - 2.51
    #5 - 2.91
    #6 - 3.04
    #1 - 3.27
    #4 - 4.55
    #3 - 9.11

Add a Comment

As it will appear on the website

Not displayed

Your website