29

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

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做到这一点?

4

7 回答 7

41

使用的替代方法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]);
于 2013-06-25T22:49:56.983 回答
22

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

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. 必须接受枚举作为泛型类型(如何解决
  2. 必须可用于集合初始化语法(如何
  3. 当我们的比较器中没有枚举值(或某些枚举值不在我们的比较器中)时,必须按默认顺序排序

所以,这里是代码:

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

我们看到,我们自己的 orderBy by 比标准 order by 更懒惰(是的,它不需要对所有内容进行排序)。甚至对于全排序也更快。

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

于 2013-06-26T00:10:03.973 回答
9

除了使用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();
于 2013-06-25T23:12:53.877 回答
2

您可以避免仅仅为了实现 IComparable 而编写一个全新的类型。请改用 Comparer 类:

IComparer<Message> comparer = Comparer.Create<Message>((message) =>
    {
    // lambda that compares things
    });
tempList.Sort(comparer);
于 2013-06-26T00:52:47.023 回答
1

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

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

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

此外,如果有人决定对枚举重新编号、重构或重新排序,一切都会自动处理。

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

于 2013-06-25T22:57:48.567 回答
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();
于 2016-08-17T12:52:56.420 回答
0

如果您打算使用实体框架 (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在该临时列上创建一个子选择。

于 2018-02-14T20:16:05.977 回答