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
Func<object, bool> func = (obj) => obj.Equals(default(Int32));Func<object, bool> func = (obj) => obj.Equals(default(Int32));
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!
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… 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:
Method | Ticks | Difference |
#0: Direct Comparison | 1.82 | 131.88% |
#1: Comparison With a New Instance | 1.92 | 139.13% |
#2: Using Generics Directly | 1.46 | 105.80% |
#3: Using Generics Dynamically | 6.9 | 500% |
#4: Using a LINQ Expression Bound to a Specific Type | 3.05 | 221.01% |
#5: Using a LINQ Expression Bound to Object | 1.61 | 116.67% |
#6: Using Formatter Services | 1.53 | |
#7: Using a LINQ Expression Bound to a Specific Type and Using Invocation Through Dynamics | 1.38 | 100% |
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!