屬性是不是這是一個非常好的比賽,因爲你遇到了幾個問題:
- 您不能指定每個單獨的屬性應該如何進行比較,如果有選擇,因爲屬性需要在編譯時工作,並且不允許像
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
可能是一個更好的選擇,即使它意味着更多的代碼(如果這是個問題,您可以通過各種方式來生成代碼,例如使用T4模板)。
你必須寫一個通用的擴展方法 –
爲什麼不是list.OrderBy(x => x.Date); ? –