2

I'm trying to call a "Sort" method that expects a parameter of type IComparer<object>, using the code:

collection.Sort((IComparer<object>)Comparer<DateTime>.Default)

It builds but at run time I get an InvalidCastException with the message:

Unable to cast object of type
'System.Collections.Generic.GenericComparer`1[System.DateTime]'
to type 'System.Collections.Generic.IComparer`1[System.Object]'.

Now what?

JoelFan
  • 37,465
  • 35
  • 132
  • 205
  • Please post the declaration of "collection". We need more information to help. – Reed Copsey Sep 03 '09 at 19:51
  • It is an LLBL collection, namely SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2 (now you are sorry you asked :) – JoelFan Sep 03 '09 at 20:02
  • You'll need a custom comparer - if the types are always DateTIme values, what I just added should work (with only 5 extra lines of code) – Reed Copsey Sep 03 '09 at 20:09
  • 2
    ... also, no, this won't work in C# 4.0 either (because, while `IComparer` will be contravariant, it won't be covariant - and this code tries to treat it as covariant). – Pavel Minaev Sep 03 '09 at 23:07
  • 1
    @Pavel: And also because DateTime is a value type, and variance only works over reference types :) – Jon Skeet Sep 04 '09 at 05:25

5 Answers5

7

If all you want is the default comparison, this will work:

collection.Sort(Comparer<object>.Default)

Comparer.Default uses your objects' inherent comparison semantics (i.e., IComparable.CompareTo).

Av Pinzur
  • 2,188
  • 14
  • 14
4

Unfortunately, you need to have a comparer of the appropriate type.

You could make a custom IComparer<object> class that just wrapped the DateTime comparer, but there is no way to do this directly via a cast.

If your collection always contains DateTime objects, then you can just do:

ICollection<DateTime> collection = ...;
collection.Sort(Comparer<DateTime>.Default); // or just collection.Sort()

Edit after reading the comment:

If you're working with an ICollection directly, you may want to use the LINQ option to do:

collection.Cast<DateTime>().OrderBy( date => date );

If you're working with something that implements IList<T>, (such as List<DateTime>) you can just call Sort() on the list itself.


Since you're using a non-standard class, you'll need to make your own comparer:

class Comparer : IComparer<object> {
    int Compare(object a, object b) {
         return DateTime.Compare((DateTime)a, (DateTime)b);
    }
}

You can then call:

collection.Sort(new Comparer() );
Reed Copsey
  • 554,122
  • 78
  • 1,158
  • 1,373
  • I got: 'System.Collections.Generic.ICollection' does not contain a definition for 'Sort' and no extension method 'Sort' accepting a first argument of type 'System.Collections.Generic.ICollection' could be found (are you missing a using directive or an assembly reference?) – JoelFan Sep 03 '09 at 19:17
  • The LINQ option won't work because I want an "in-memory" sort (LINQ OrderBy returns a new collection). And as for the second suggestion, no it doesn't implement IList :( – JoelFan Sep 03 '09 at 19:46
  • What type is your collection? Can you put its declaration in place? – Reed Copsey Sep 03 '09 at 19:51
  • Edited again, since we know the collection type. – Reed Copsey Sep 03 '09 at 20:08
  • Hooray for co and contra variance in C# 4.0! – tsilb Sep 03 '09 at 21:28
3

If you can change the type of the collection object (i.e. to an ArrayList from a List<object>), then you can just use the non-generic IComparer interface (which Comparer<DateTime>.Default implements).

If you can't change the type of the collection object, then you are out of luck. (You could always implement an object that implements IComparer<object>, like so:

 public class DateTimeComparer : IComparer<object>
    {       
        public int Compare(object x, object y)
        {
            IComparer myComparer = Comparer<DateTime>.Default as IComparer;
            return myComparer.Compare(x, y);
        }     
    }

(This will throw an exception if you have any non-DateTime items in your collection though)

EDIT:

You could also implement something a little bit more type safe, i.e.:

  public class DateTimeComparer : IComparer<object>
    {       
        public int Compare(object x, object y)
        {
            if ( x is DateTime && y is DateTime )
            {
                return Comparer<DateTime>.Default.Compare((DateTime) x, (DateTime) y);
            }
            else
            {
                // handle the type mismatch
            }
        }     
    }
Mike Sackton
  • 1,094
  • 7
  • 19
3

You can define an extension function like this:

public static class ComparerConversion
    {
        private class ComparerWrapper<T> : IComparer<object>
        {
            private readonly IComparer<T> comparer;

            public ComparerWrapper(IComparer<T> comparer)
            {
                this.comparer = comparer;
            }

            public int Compare(object x, object y)
            {
                return this.comparer.Compare((T)x, (T)y);
            }
        }

        public static IComparer<object> ToObjectComparer<T>(this IComparer<T> comparer)
        {
            return new ComparerWrapper<T>(comparer);
        }
    }

and use it like this:

List<object> collection = new List<object> { DateTime.Now.AddDays(1), DateTime.Now };
collection.Sort(Comparer<DateTime>.Default.ToObjectComparer());
JoelFan
  • 37,465
  • 35
  • 132
  • 205
Claudiu Mihaila
  • 1,322
  • 8
  • 17
1

Just try to remove the cast and let the compiler to chose IComparer instead of IComparer<T>.

Comparer<T> implements both IComparer<T> and IComparer so it should work.

This works just fine:

ArrayList collection = new ArrayList {DateTime.Now.AddDays(1), DateTime.Now};
collection.Sort(Comparer<DateTime>.Default);
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
Claudiu Mihaila
  • 1,322
  • 8
  • 17