LINQ OrderBy анонимный объект с проекционным компаратором

Я пытаюсь получить OrderBy в операторе LINQ для работы с анонимным объектом, но до этого не удалось.

Я уже проверил их:
Анонимная реализация IComparer
С# linq sort - быстрый способ создания экземпляра IComparer
Как отсортировать массив объекта по определенному полю в С#?

Я провел несколько часов, пытаясь использовать разные подходы, но мне нужно что-то упустить.

Скажем, что существует следующий класс:

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

И products - список этих объектов.

Как я могу заполнить этот оператор LINQ, чтобы он работал с анонимным объектом?
Чтобы быть ясным, я знаю, что могу сделать это по-другому, но мне было бы очень интересно узнать, как заставить этот конкретный пример работать.

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

Кажется, что это возможно с реализацией ProjectionComparer:
http://code.google.com/p/edulinq/source/browse/src/Edulinq/ProjectionComparer.cs?r=0c583631b709679831c99df2646fc9adb781b2be

Любые идеи, как это сделать?

UPDATE:

Я сделал быстрый тест производительности на этом - анонимное решение для сравнения по сравнению с стандартным orderby.thenby, и кажется, что анонимное решение довольно медленное, что, вероятно, мы ожидали в любом случае.

         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

Ответ 1

Вы можете создать реализацию IComparer<T>, которая использует делегат, который вы поставляете для сравнения, и создать экземпляр с типом вывода (аналогично "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); }
    }
}

И используйте его таким образом:

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: Я только что просмотрел страницу сравнения проекторов, с которой вы связались. При таком подходе вам не нужен аргумент "example" для вывода типа. Однако подход должен быть адаптирован, чтобы вместо делегата взять делегат. Вот он:

//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);
    }
}

Ответ 2

Вам действительно не нужен анонимный объект для заказа этих объектов пополняющим нисходящим, а затем ценой, вы можете использовать OrerBy и ThenBy в комбинации, например:

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

Чтобы сделать IComparer<T> в анонимном типе, вам лучше всего использовать factory, чтобы построить его из делегата и использовать вывод типа (указать анонимные типы без вывода - это боль!).

Возможно, вы захотите измерить влияние производительности на создание анонимных объектов исключительно для упорядочения, но ответ Phoogs дает хороший способ использовать делегат Comparison<T> для создания IComparer<T> на лету.

Ответ 3

Не совсем ответ... но слишком долго для комментариев: трудно создать разумный общий компаньон.

Несмотря на то, что для сопоставления объектов по единому свойству существует хорошо установленное сравнение, нет такой вещи для нескольких или даже двух свойств. То есть это очень распространенная проблема, когда вы пытаетесь упорядочить точки на плоской поверхности: всего 2 значения (x, y), но нет способа сказать (x1, y1) < (x2, y2), поэтому все согласны с ним.

В большинстве случаев вы вызываете порядок по атрибуту 1, а не по атрибуту 2,... или путем сопоставления всех атрибутов с одним значением (т.е. простым умножением всех их). Эти подходы легко выражаются без использования общего сравнения в LINQ:

  • упорядочение по атрибутам с прикованным OrderBy (attr1).OrderBy(attr2)....
  • упорядочивание по метрике OrderBy (attr1 * attr2) (или любой другой Metric для ваших объектов)