2016-04-20 201 views
1

我有很多簡單的類需要排序。例如:類定義自定義排序順序

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

爲什麼不是list.OrderBy(x => x.Date); ? –

回答

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可能是一個更好的選擇,即使它意味着更多的代碼(如果這是個問題,您可以通過各種方式來生成代碼,例如使用T4模板)。

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); 
    } 
    } 
+0

我看不到如何檢查引用相等將排序。您需要根據「Date」比較返回-1或0或1。 – Crowcoder

+0

@Crowcoder - 這是標準模式。檢查'this'和'other'是否引用相同的東西,如果是的話,它們是相同的(分類到同一個地方)=>'0'。否則,檢查'other'是否爲NullReference。空值總是小於任何值,所以'this'在=>'1'後出現。現在我們知道'this'和'other'是不同的,而不是null,請進行「真實」比較,即比較日期。 – Corak

+0

@Dmitry Bychenko,我不同意。如果您想通過日期屬性對您的類實例進行排序,那麼引用相等沒有任何區別。然後不得不比較日期呢?我想看看你是如何完成這種排序的實施和對「標準模式」的參考。 – Crowcoder