我有很多需要排序的简单类。例如:
IEnumerable<Sortsample> OutputList = List.OrderBy(x => x);
所有这些类只需要按照如下定义的属性进行排序:
class Sortsample
{
[SortAttribute] //Item should be sorted by Date
public DateTime Date { get; set; }
public string Name { get; set; }
}
是否有类似[SortAttribute]
(或类似的简单方法)或我必须为每个类实施CompareTo
?
答案 0 :(得分:4)
我建议实施IComparable<Sortsample>
:
class Sortsample: IComparable<Sortsample> {
public DateTime Date {
get; set;
}
public string Name {
get; set;
}
public int CompareTo(Sortsample other) {
// if "other" is, in fact, "this"
if (Object.ReferenceEquals(this, other))
return 0;
else if (Object.ReferenceEquals(null, other))
return 1; // let's consider that "this" (not null) > null
// other is not this, and other is not null
return Date.CompareTo(other.Date);
}
}
答案 1 :(得分:2)
属性不是很好的匹配,因为你遇到了几个问题:
IComparer
这样的复杂对象通过。你必须依赖默认值。对于字符串,您可以使用StringComparison
来解决此问题,IComparable
是一个枚举,可以传递给属性。IComparable
,因此任何依赖IComparable
订购的代码都无法使用您的自定义排序。理论上,您可以通过基类实现.OrderBy()
,但如果您需要与现有对象层次结构集成,则这不起作用。一般来说,如果只有一种合理的订购方式,明确地将方法传递给IComparable<T>
或实施IComparer
是更好的选择。但是,所有这些都说:是的,你可以制作它,以便你可以根据类中的属性订购实例,方法是按需生成[AttributeUsage(AttributeTargets.Property)]
class SortKeyAttribute : Attribute { }
。
首先定义一个简单的属性:
class SortSample {
[SortKey]
public DateTime Date { get; set; }
public string Name { get; set; }
}
要像这样使用,例如:
.OrderByKey()
让我们假设我们有一个扩展方法[SortKey]
,可以通过应用IEnumerable<SortSample> outputList = list.OrderByKey();
的属性为我们排序实例,然后我们就可以这样调用它:
public static class EnumerableExtensions {
public static IOrderedEnumerable<T> OrderByKey<T>(this IEnumerable<T> sequence) {
return sequence.OrderBy(x => x, getKeyComparer<T>());
}
static IComparer<T> createComparer<T, TKey>(PropertyInfo key) {
Func<T, TKey> keySelector = x => (TKey) key.GetValue(x);
var keyComparer = Comparer<TKey>.Default;
return Comparer<T>.Create(
(x, y) => keyComparer.Compare(keySelector(x), keySelector(y))
);
}
static IComparer<T> getKeyComparer<T>() {
List<PropertyInfo> sortKeyProperties = (
from p in typeof(T).GetProperties()
let a = (SortKeyAttribute) p
.GetCustomAttributes(typeof(SortKeyAttribute))
.FirstOrDefault()
where a != null
select p
).ToList();
if (sortKeyProperties.Count == 0) {
// with no [SortKey], fall back to the default comparer
return Comparer<T>.Default;
}
if (sortKeyProperties.Count > 1) {
throw new InvalidOperationException(
$"Multiple sort keys specified for class '{typeof(T).FullName}'."
);
}
PropertyInfo sortKeyProperty = sortKeyProperties[0];
MethodInfo createComparerMethodInfo = typeof(EnumerableExtensions)
.GetMethod("createComparer", BindingFlags.NonPublic | BindingFlags.Static)
;
return (IComparer<T>) createComparerMethodInfo
.MakeGenericMethod(typeof(T), sortKeyProperty.PropertyType)
.Invoke(null, new[] { sortKeyProperty })
;
}
}
该方法的一种可能的实现如下:
IComparer
一些注意事项:
Comparer.Default
创建IComparer.CompareTo
实例。这是相当慢的,但我们可以预期排序主要花费在Reflection.Emit
上的时间,而这并不是通过反思来实现的。尽管如此,可以使用标准方法优化此代码以优化反射(SortKey
,表达式树,在静态字典中缓存生成的实例)。[SortKey(1)]
属性,因此您可以编写StringComparison
为例。getKeyComparer
参数。这并没有扩展到其他类型。IComparer<T>
,这样您就可以使用带IComparable<T>
的方法(但如果您经常这样做,您也可以实现IComparable
)。< / LI>
重申一下,这可能看起来很聪明,它可以回答你的问题,但是在生产代码中,由于我在开始时提到的原因,我不会使用属性进行排序。在我看来,缺点超过了好处,直接实现{{1}}可能是一个更好的选择,即使它意味着更多的代码(如果这是一个问题,你可以看看生成代码的各种方法) ,与T4模板一样)。