Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does the default comparator work in C#?

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?

like image 565
cheft Avatar asked Aug 11 '15 22:08

cheft


People also ask

How does comparator work in C?

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).

How does qsort work in C?

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.

How does comparator function works in sorting?

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.


2 Answers

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.

like image 133
Scott Chamberlain Avatar answered Oct 05 '22 12:10

Scott Chamberlain


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 objects).

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.

like image 33
Etienne de Martel Avatar answered Oct 05 '22 11:10

Etienne de Martel