按枚举排序列表,其中枚举无序

我有一个消息列表。 每条消息都有一个类型。

public enum MessageType { Foo = 0, Bar = 1, Boo = 2, Doo = 3 } 

枚举名称是任意的,不能更改。

我需要返回列表排序为:Boo,Bar,Foo,Doo

我目前的解决方案是创建一个tempList,按我想要的顺序添加值,返回新列表。

 List tempList = new List(); tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Boo)); tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Bar)); tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Foo)); tempList.AddRange(messageList.Where(m => m.MessageType == MessageType.Doo)); messageList = tempList; 

我怎么能用IComparer做到这一点?

那么,让我们编写自己的比较器:

 public class MyMessageComparer : IComparer { protected IList orderedTypes {get; set;} public MyMessageComparer() { // you can reorder it's all as you want orderedTypes = new List() { MessageType.Boo, MessageType.Bar, MessageType.Foo, MessageType.Doo, }; } public int Compare(MessageType x, MessageType y) { var xIndex = orderedTypes.IndexOf(x); var yIndex = orderedTypes.IndexOf(y); return xIndex.CompareTo(yIndex); } }; 

如何使用:

 messages.OrderBy(m => m.MessageType, new MyMessageComparer()) 

有一种更简单的方法:只需创建ordereTypes列表并使用OrderBy的另一个重载:

 var orderedTypes = new List() { MessageType.Boo, MessageType.Bar, MessageType.Foo, MessageType.Doo, }; messages.OrderBy(m => orderedTypes.IndexOf(m.MessageType)).ToList(); 

嗯..让我们尝试通过编写我们自己的IComparer来获得优势。 想法:把它写成我们的最后一个例子,但是在其他一些语义中。 像这样:

 messages.OrderBy( m => m.MessageType, new EnumComparer() { MessageType.Boo, MessageType.Foo } ); 

或这个:

 messages.OrderBy(m => m.MessageType, EnumComparer()); 

好的,我们需要什么。 我们自己的比较器:

  1. 必须接受enum作为generics类型( 如何解决 )
  2. 必须可用于集合初始化程序语法( 如何 )
  3. 当我们的比较器中没有枚举值时(或者某些枚举值不在我们的比较器中),必须按默认顺序排序

所以,这是代码:

 public class EnumComparer: IComparer, IEnumerable where TEnum: struct, IConvertible { protected static IList TypicalValues { get; set; } protected IList _reorderedValues; protected IList ReorderedValues { get { return _reorderedValues.Any() ? _reorderedValues : TypicalValues; } set { _reorderedValues = value; } } static EnumComparer() { if (!typeof(TEnum).IsEnum) { throw new ArgumentException("T must be an enumerated type"); } TypicalValues = new List(); foreach (TEnum value in Enum.GetValues(typeof(TEnum))) { TypicalValues.Add(value); }; } public EnumComparer(IList reorderedValues = null) { if (_reorderedValues == null ) { _reorderedValues = new List(); return; } _reorderedValues = reorderedValues; } public void Add(TEnum value) { if (_reorderedValues.Contains(value)) return; _reorderedValues.Add(value); } public int Compare(TEnum x, TEnum y) { var xIndex = ReorderedValues.IndexOf(x); var yIndex = ReorderedValues.IndexOf(y); // no such enums in our order list: // so this enum values must be in the end // and must be ordered between themselves by default if (xIndex == -1) { if (yIndex == -1) { xIndex = TypicalValues.IndexOf(x); yIndex = TypicalValues.IndexOf(y); return xIndex.CompareTo(yIndex); } return -1; } if (yIndex == -1) { return -1; // } return xIndex.CompareTo(yIndex); } public void Clear() { _reorderedValues = new List(); } private IEnumerable GetEnumerable() { return Enumerable.Concat( ReorderedValues, TypicalValues.Where(v => !ReorderedValues.Contains(v)) ); } public IEnumerator GetEnumerator() { return GetEnumerable().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerable().GetEnumerator(); } } 

所以,好吧,让我们更快地进行排序。 我们需要为我们的枚举覆盖默认的OrderBy方法:

 public static class LinqEnumExtensions { public static IEnumerable OrderBy(this IEnumerable source, Func selector, EnumComparer enumComparer) where TEnum : struct, IConvertible { foreach (var enumValue in enumComparer) { foreach (var sourceElement in source.Where(item => selector(item).Equals(enumValue))) { yield return sourceElement; } } } } 

是的,那很懒。 你可以谷歌收益率如何运作。 好吧,让我们测试速度吧。 简单的基准: http : //pastebin.com/P8qaU20Y 。 结果为n = 1000000;

 Enumerable orderBy, elementAt: 00:00:04.5485845 Own orderBy, elementAt: 00:00:00.0040010 Enumerable orderBy, full sort: 00:00:04.6685977 Own orderBy, full sort: 00:00:00.4540575 

我们看到,我们自己的订单通过更为懒惰的标准顺序(是的,它不需要对所有东西进行排序)。 甚至对于fullsort也更快。

此代码中的问题:它不支持ThenBy() 。 如果你需要这个,你可以编写自己的linq扩展,返回IOrderedEnumerable Jon Skeet有一个博客文章系列,它深入LINQ to Objects,提供了一个完整的替代实现。 第26a和26b 部分介绍了IOrderedEnumerable的基础,并在26c和26d中提供了更多细节和优化。

使用IComparer的另一种方法是构建一个排序字典。

 var orderMap = new Dictionary() { { MessageType.Boo, 0 }, { MessageType.Bar, 1 }, { MessageType.Foo, 2 }, { MessageType.Doo, 3 } }; var orderedList = messageList.OrderBy(m => orderMap[m.MessageType]); 

如果您具有固定数量的消息类型,则还可以使用SelectMany方法而不是使用IComparer ,该方法应该具有更好的大型消息列表性能。

 var messageTypeOrder = new [] { MessageType.Boo, MessageType.Bar, MessageType.Foo, MessageType.Doo, }; List tempList = messageTypeOrder .SelectMany(type => messageList.Where(m => m.MessageType == type)) .ToList(); 

您可以避免编写一个全新的类型来实现IComparable。 改为使用Comparer类:

 IComparer comparer = Comparer.Create((message) => { // lambda that compares things }); tempList.Sort(comparer); 

您可以使用LINQEnum值动态构建映射字典,如下所示:

  var mappingDIctionary = new List((string[])Enum.GetNames(typeof(Hexside))) .OrderBy(label => label ) .Select((i,n) => new {Index=i, Label=n}).ToList(); 

现在,添加到Enum n future的任何新值都将自动正确映射。

此外,如果有人决定对枚举进行重新编号,重构或重新排序,则会自动处理所有内容。

更新:如下所述,没有要求按字母排序; 而是一个半字母顺序,所以基本上是随机的。 虽然不是这个特定问题的答案,但这种技术可能对未来的访问者有用,所以我会让它保持原状。

无需映射。 这应该给你基于枚举的列表和顺序。 即使更改枚举的顺序或新项目,也无需修改任何内容……

 var result = (from x in tempList join y in Enum.GetValues(typeof(MessageType)).Cast() on x equals y orderby y select y).ToList(); 

如果您要使用Entity Framework(EF),那么您必须在OrderBy展开您的枚举:

 messageList.OrderBy(m => m.MessageType == MessageType.Boo ? 0 : m.MessageType == MessageType.Bar ? 1 : m.MessageType == MessageType.Foo ? 2 : m.MessageType == MessageType.Doo ? 3 : 4 ); 

这将创建一个带有CASE WHEN的子选择,然后在该临时列上创建ORDER BY