Domanda

Ive been trying to get OrderBy in a LINQ statement to work with an anonymous object but have failed by now.

I checked these already:
Anonymous IComparer implementation
C# linq sort - quick way of instantiating IComparer
How to sort an array of object by a specific field in C#?

I spent a few hours trying different approaches but there has to be something I'm missing.

Let's say there's the following class:

public class Product
{
   public int Id {get; set;}
   public string Name {get; set;}
   public int Popularity {get; set;}
   public decimal Price {get; set;}
}

And products is a list of these objects.

How can I complete this LINQ statement, so that it works with the anonymous object ?
To be clear, I know I can do this in a different way but I'd be very interested to learn how to make this particular example work.

var sortedProducts = products
                       .OrderBy(p => 
                              new {p.Popularity, p.Price}, 
                              [IComparer magic goes here]);

It seems that it should be possible with an implementation of the ProjectionComparer:
http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be

Any ideas how to do this ?

UPDATE:

I did a quick performance test on this - the anonymous comparer solution vs standard orderby.thenby and it seems that the anonymous solution is quite slower which is probably what we might have expected anyway.

         numProd  | Anon    | chained orderby clauses
         10 000   | 47 ms   | 31 ms
         100 000  | 468 ms  | 234 ms
         1 000 000| 5818 ms | 2387 ms
         5 000 000| 29547 ms| 12105 ms
È stato utile?

Soluzione

You can make an IComparer<T> implementation that uses a delegate that you supply for the comparison, and instantiate it with type inference (similar to "cast by example"):

static class AnonymousComparer
{
    public static IComparer<T> GetComparer<T>(T example, Comparison<T> comparison)
    {
        return new ComparerImpl<T>(comparison);
    }
    private class ComparerImpl<T> : IComparer<T>
    {
        private readonly Comparison<T> _comparison;
        public ComparerImpl(Comparison<T> comparison) { _comparison = comparison; }
        public int Compare(T x, T y) { return _comparison.Invoke(x, y); }
    }
}

And use it thus:

var comparer = AnonymousComparer.GetComparer(
    new { Popularity = 0, Price = 0m },
    (a, b) => //comparison logic goes here
    );

var sortedProducts = products
    .OrderBy(p =>
        new { p.Popularity, p.Price },
        comparer); 

EDIT: I just checked out the projection comparer page you linked to. With that approach, you don't need the "example" argument for type inference. The approach still needs to be adapted, however, to take a delegate instead of an interface. Here it is:

//adapted from http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be
static class AnonymousProjectionComparer
{
    private class ProjectionComparer<TElement, TKey> : IComparer<TElement>
    {
        private readonly Func<TElement, TKey> keySelector;
        private readonly Comparison<TKey> comparison;

        internal ProjectionComparer(Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
        {
            this.keySelector = keySelector;
            this.comparison = comparison ?? Comparer<TKey>.Default.Compare;
        }

        public int Compare(TElement x, TElement y)
        {
            TKey keyX = keySelector(x);
            TKey keyY = keySelector(y);
            return comparison.Invoke(keyX, keyY);
        }
    }

    public static IComparer<TElement> GetComparer<TElement, TKey>(Func<TElement, TKey> keySelector, Comparison<TKey> comparison)
    {
        return new ProjectionComparer<TElement, TKey>(keySelector, comparison);
    }
}

Altri suggerimenti

You don't really need an anonymous object to order these objects by populartiy descending and then price, you can use OrerBy and ThenBy in combination, like:

var sortedProducts = products.OrderByDescending(p => p.Popularity)
    .ThenBy(p => p.Price);

To do an IComparer<T> on an anonymous type, you'd be best off using a factory to construct one from a delegate and using type inference (specifying anonymous types without inference is a pain!).

You might want to measure the performance implications of creating anonymous objects purely for ordering, but Phoogs answer gives a good way to use Comparison<T> delegate to construct an IComparer<T> on the fly..

Not exactly an answer... but too long for comment: It is hard to create sensible generic comparer.

While there is well established comparison relation for objects by single property there no such thing for multiple or even 2 properties. I.e. this is very common problem when you try to order points on flat surface: just 2 values (x,y) but there is no way to say (x1,y1) < (x2,y2) so everyone agrees with it.

In most cases you end up saying order by attribute 1, than by attribute 2,... or by mapping all attributes to single value (i.e. by simply multiplying all of them). These approaches are easily expressed without need of generic comparer in LINQ:

  • ordering by attributes with chained OrderBy(attr1).OrderBy(attr2)....
  • ordering by metric OrderBy(attr1 * attr2) (or any other Metric on your objects)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top