2016-04-20 2 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] (или аналогичный простой способ) или же я должен implemet 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

Я не вижу, как будет проверяться ссылочное равенство. Вам нужно будет вернуть -1 или 0 или 1 на основе сравнения «Дата». – Crowcoder

+0

@Crowcoder - это стандартный шаблон. Проверьте, являются ли эти и другие ссылки одинаковыми, если они равны (сортируются в одно и то же место) => '0'. В противном случае проверьте, является ли 'other' NullReference. Нули всегда меньше, чем что угодно, поэтому 'this' приходит после =>' 1'. Теперь, когда мы знаем, что 'this' и' other' отличаются друг от друга, а не null, выполните «реальное» сравнение, то есть сравните даты. – Corak

+0

@ Дмитрий Биченко, я не согласен. Ссылка равенство не имеет никакого значения, если вы хотите, чтобы отсортировать экземпляры классов по свойству даты. А потом все равно сравнивать даты? Я хотел бы видеть, как вы завершаете реализацию сортировки и ссылку на «стандартный шаблон». – Crowcoder

Смежные вопросы