类定义自定义排序顺序

时间:2016-04-20 10:00:49

标签: c# list sorting

我有很多需要排序的简单类。例如:

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

2 个答案:

答案 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是一个枚举,可以传递给属性。
  • 处理属性意味着使用反射和通用代码。如果你不是很关心性能而且非常努力,那么这很容易。它还引入了更多&#34;魔术&#34;这可能会使维护者更难理解代码。
  • 由于您的课程不会实施IComparable,因此任何依赖IComparable订购的代码都无法使用您的自定义排序。理论上,您可以通过基类实现.OrderBy(),但如果您需要与现有对象层次结构集成,则这不起作用。
  • 你只是用一种特殊的方式来订购物品。如果您认为这是&#34;默认&#34;这是可以的。订购并在必要时进行自定义比较。

一般来说,如果只有一种合理的订购方式,明确地将方法传递给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模板一样)。