LINQ OrderBy 具有投影比较器的匿名对象

本文关键字:对象 比较器 投影 OrderBy LINQ | 更新日期: 2023-09-27 18:32:43

我一直试图在 LINQ 语句中获取OrderBy以使用匿名对象,但现在失败了。

我已经检查了这些:
匿名 IComparer 实现
C# linq sort - 实例化 IComparer
的快速方法如何在 C# 中按特定字段对对象数组进行排序?

花了几个小时尝试不同的方法,但一定有一些我错过的东西。

假设有以下类:

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

任何想法如何做到这一点?

更新:

我对此进行了快速性能测试 - 匿名比较器解决方案与标准 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

LINQ OrderBy 具有投影比较器的匿名对象

您可以创建一个IComparer<T>实现,该实现使用您提供的用于比较的委托,并使用类型推断对其进行实例化(类似于"按示例强制转换"(:

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

编辑:我刚刚查看了您链接到的投影比较器页面。 使用这种方法,您不需要类型推断的"示例"参数。 但是,该方法仍然需要调整,以采用委托而不是接口。 在这里:

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

你真的不需要一个匿名对象来按流行降序和价格对这些对象进行排序,你可以结合使用 OrerBy 和 ThenBy,例如:

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

若要对匿名类型执行IComparer<T>,最好使用工厂从委托构造一个工厂并使用类型推断(指定匿名类型而不进行推理是一种痛苦!

您可能希望衡量纯粹为了排序而创建匿名对象的性能影响,但 Phoogs 答案提供了一种使用委托动态构建IComparer<T> Comparison<T>很好的方法。

不完全是答案...但评论太长:很难创建合理的通用比较器。

虽然对象按单个属性建立良好的比较关系,但对于多个甚至 2 个属性,则没有这样的事情。 也就是说,当您尝试在平面上对点进行排序时,这是非常常见的问题:只有 2 个值 (x,y(,但没有办法说 (x1,y1( <(x2,y2(,所以每个人都同意它。

在大多数情况下,您最终会按属性 1 而不是按属性 2 表示顺序,...或者将所有属性映射到单个值(即简单地将所有属性相乘(。这些方法很容易表达,而无需在 LINQ 中使用泛型比较器:

  • 使用链式 OrderBy(attr1( 按属性排序。OrderBy(attr2(....
  • 按指标排序 OrderBy(attr1 * attr2((或对象上的任何其他指标(