views:

636

answers:

5

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?

+4  A: 

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
I got: 'System.Collections.Generic.ICollection<System.DateTime>' does not contain a definition for 'Sort' and no extension method 'Sort' accepting a first argument of type 'System.Collections.Generic.ICollection<System.DateTime>' could be found (are you missing a using directive or an assembly reference?)
JoelFan
@JoelFan: I edited my answer in response
Reed Copsey
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<T> :(
JoelFan
What type is your collection? Can you put its declaration in place?
Reed Copsey
Edited again, since we know the collection type.
Reed Copsey
Hooray for co and contra variance in C# 4.0!
tsilb
+1  A: 

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);
Claudiu
I get: cannot convert from 'System.Collections.Generic.Comparer<System.DateTime>' to 'System.Collections.Generic.IComparer<object>
JoelFan
just to be clear... I am not using ArrayList like you are
JoelFan
Are you trying to use a Sort function that has this Sort(IComparer<T> comparer>) signature or Sort(IComparer comparer)?What is the type of your collection?
Claudiu
it expects IComparer<object>. It is an LLBL collection, namely SD.LLBLGen.Pro.ORMSupportClasses.EntityCollectionBase2<TEntity> (now you are sorry you asked :)
JoelFan
Than your only option is to create the wrapper
Claudiu
+2  A: 

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
+2  A: 

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());
Claudiu
+2  A: 

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

Abraham Pinzur
Will that compare 2 DateTime's correctly?
JoelFan
Yes it will. I edited my response to clarify this.
Abraham Pinzur
Wow I tried it and it works! I don't understand why though!
JoelFan