LINQ OrderBy具有投影比较器的匿名对象
我一直试图在LINQ语句中使用OrderBy
来处理匿名对象,但现在失败了。
我已经检查过这些:
匿名IComparer实现
C#linq排序 - 快速实例化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
任何想法如何做到这一点?
更新:
我对此做了一个快速性能测试 - 匿名比较解决方案vs标准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
您可以创建一个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答案提供了一种使用Comparison<T>
委托来即时构建IComparer<T>
的好方法..
不完全是一个答案......但太长的评论:很难创建明智的通用比较器。
虽然单个属性之间存在良好的对象关系,但是对于多个属性或者甚至是两个属性都没有这种东西。 即当你尝试在平坦的表面上排序点时,这是非常普遍的问题:只有2个值(x,y),但没有办法说(x1,y1)<(x2,y2),所以每个人都同意它。
在大多数情况下,您最终会按属性1排序,而不是按属性2排序,或者通过将所有属性映射到单个值(即通过简单地将它们全部相乘)。 这些方法很容易表达,而不需要LINQ中的通用比较器: