Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ OrderBy anonymous object with projection comparer

Tags:

c#

linq

icomparer

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
like image 907
Joanna Derks Avatar asked Apr 27 '12 19:04

Joanna Derks


2 Answers

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);
    }
}
like image 188
phoog Avatar answered Nov 15 '22 02:11

phoog


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

like image 37
James Michael Hare Avatar answered Nov 15 '22 00:11

James Michael Hare