按enum排序列表<t>,其中枚举无序</t>

时间:2013-06-25 22:44:47

标签: c# sorting

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

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

枚举名称是任意的,无法更改。

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

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

List<Message> tempList = new List<Message>();
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

执行此操作

7 个答案:

答案 0 :(得分:38)

使用IComparer的替代方法是构建排序字典。

var orderMap = new Dictionary<MessageType, int>() {
    { MessageType.Boo, 0 },
    { MessageType.Bar, 1 },
    { MessageType.Foo, 2 },
    { MessageType.Doo, 3 }
};

var orderedList = messageList.OrderBy(m => orderMap[m.MessageType]);

答案 1 :(得分:17)

所以,让我们编写自己的比较器:

public class MyMessageComparer : IComparer<MessageType> {
    protected IList<MessageType> orderedTypes {get; set;}

    public MyMessageComparer() {
        // you can reorder it's all as you want
        orderedTypes = new List<MessageType>() {
            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>() {        
            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>() { 
          MessageType.Boo, 
          MessageType.Foo }
);

或者这个:

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

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

  1. 必须接受枚举作为通用类型(how to solve
  2. 必须可用于集合初始值设定项语法(how to
  3. 当我们的比较器中没有枚举值(或者某些枚举值不在我们的比较器中)时,必须按默认顺序排序
  4. 所以,这是代码:

    public class EnumComparer<TEnum>: IComparer<TEnum>, IEnumerable<TEnum> where TEnum: struct, IConvertible {
        protected static IList<TEnum> TypicalValues { get; set; }
    
        protected IList<TEnum> _reorderedValues;
    
        protected IList<TEnum> 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<TEnum>();
            foreach (TEnum value in Enum.GetValues(typeof(TEnum))) {
                TypicalValues.Add(value);
            };            
        }
    
        public EnumComparer(IList<TEnum> reorderedValues = null) {
            if (_reorderedValues == null ) {
                _reorderedValues = new List<TEnum>();
    
                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<TEnum>();
        }
    
        private IEnumerable<TEnum> GetEnumerable() {
            return Enumerable.Concat(
                ReorderedValues,
                TypicalValues.Where(v => !ReorderedValues.Contains(v))
            );
        }
    
        public IEnumerator<TEnum> GetEnumerator() {
            return GetEnumerable().GetEnumerator();            
        }
    
        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerable().GetEnumerator();            
        }
    }
    

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

    public static class LinqEnumExtensions
    {
        public static IEnumerable<TSource> OrderBy<TSource, TEnum>(this IEnumerable<TSource> source, Func<TSource, TEnum> selector, EnumComparer<TEnum> 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有一个blog post series by Jon Skeet深入LINQ to Objects,提供了一个完整的替代实现。 IOrderedEnumerablepart 26a涵盖{{1}}的基础,26b26c中包含更多详情和优化。

答案 2 :(得分:8)

如果您拥有固定数量的邮件类型,则还可以使用IComparer方法,而不是使用SelectMany,这对于大型邮件列表应具有更好的性能。

var messageTypeOrder = new [] {
    MessageType.Boo,
    MessageType.Bar,
    MessageType.Foo,
    MessageType.Doo,
};

List<Message> tempList = messageTypeOrder
    .SelectMany(type => messageList.Where(m => m.MessageType == type))
    .ToList();

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

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

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

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

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

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

答案 5 :(得分:0)

无需映射。这应该给你基于枚举的列表和顺序。即使您更改了枚举订单或新商品,也无需修改任何内容......

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

答案 6 :(得分:0)

如果您要使用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