атрибут не очень хорошо подходят для этого, потому что вы столкнетесь с несколькими проблемами:
- Вы не можете определить, как каждый человек должен недвижимость сравниваться, если есть выбор, потому что атрибуты должны работать во время компиляции и не позволяют передавать сложные объекты, такие как
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); ? –