I'm using OrderBy for some sorting based on properties and I found the documentation for the default comparer but it didn't explain much to me. If an object doesn't implement System.IComparable<T>
, how does it generate a Comparer<T>
?
For instance, I'm currently sorting a list of objects based a property value of type object
. They are numeric types underneath and the sorting works fine. How does C#/Linq know how to sort the object? Does it do some un-boxing into primitives? Does it do some hash checking? How would that translate into greater than or less than?
If they were a more complex type would this fail with an error or would OrderBy do nothing, or would it even sort in a way that made no sense?
The comparator function takes two arguments and contains logic to decide their relative order in sorted output. The idea is to provide flexibility so that qsort() can be used for any type (including user defined types) and can be used to obtain any desired order (increasing, decreasing or any other).
The qsort() function sorts an array of num elements, each of width bytes in size, where the first element of the array is pointed to by base. The compare pointer points to a function, which you supply, that compares two array elements and returns an integer value specifying their relationship.
If you compare age first and then compare name if the ages are equal, the sequence will be sorted by age -- but within each subsequence where the age is equal, that subsequence is sorted by name.
Well you can check the reference source and see for yourself what it does.
public static Comparer<T> Default {
get {
Contract.Ensures(Contract.Result<Comparer<T>>() != null);
Comparer<T> comparer = defaultComparer;
if (comparer == null) {
comparer = CreateComparer();
defaultComparer = comparer;
}
return comparer;
}
}
private static Comparer<T> CreateComparer() {
RuntimeType t = (RuntimeType)typeof(T);
// If T implements IComparable<T> return a GenericComparer<T>
#if FEATURE_LEGACYNETCF
//(SNITP)
#endif
if (typeof(IComparable<T>).IsAssignableFrom(t)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericComparer<int>), t);
}
// If T is a Nullable<U> where U implements IComparable<U> return a NullableComparer<U>
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)) {
RuntimeType u = (RuntimeType)t.GetGenericArguments()[0];
if (typeof(IComparable<>).MakeGenericType(u).IsAssignableFrom(u)) {
return (Comparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(NullableComparer<int>), u);
}
}
// Otherwise return an ObjectComparer<T>
return new ObjectComparer<T>();
}
So what it does is it checks if the type implements IComparable<T>
, if it does it uses the comparer built in to the type (your list of objects that are numeric types would follow this branch). It then does the same check again in case the type is a Nullable<ICompareable<T>>
. If that also fails it uses the ObjectComparer
which uses Comparer.Default
.
Here is the Compare code for Comparer.Default
public int Compare(Object a, Object b) {
if (a == b) return 0;
if (a == null) return -1;
if (b == null) return 1;
if (m_compareInfo != null) {
String sa = a as String;
String sb = b as String;
if (sa != null && sb != null)
return m_compareInfo.Compare(sa, sb);
}
IComparable ia = a as IComparable;
if (ia != null)
return ia.CompareTo(b);
IComparable ib = b as IComparable;
if (ib != null)
return -ib.CompareTo(a);
throw new ArgumentException(Environment.GetResourceString("Argument_ImplementIComparable"));
}
As you can see it checks if a
or b
implements IComparable
and if neither do it throws a exception.
Browsing on Reference Source, it returns an ObjectComparer<T>
, which is a special internal type that just delegates the work to System.Collections.Comparer.Default
.
This, in turn, throws an exception if it receives parameters that do not implement IComparable
. Since that comparer works through downcasting and reflection, then it does not care if the static type of the object does not implement IComparable
(which is the case if you have a list of object
s).
So the bottom line is this: first it checks for IComparable<T>
, then it checks for IComparable
, and finally it throws an exception.
By the way, most (I'd say all even) built-in types implement IComparable<T>
in some way, so that's how they can be sorted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With