按声明顺序对枚举进行排序

本文关键字:排序 枚举 声明 顺序 | 更新日期: 2023-09-27 18:16:27

public enum CurrencyId
{
    USD = 840,
    UAH = 980,
    RUR = 643,
    EUR = 978,
    KZT = 398,
    UNSUPPORTED = 0
}

是否有办法按顺序排序Enum.GetValues(typeof(CurrencyId)).Cast<CurrencyId>()的结果,他们在。cs文件中声明(美元,UAH, RUR, EUR, KZT,不支持),而不是他们的底层代码?就我个人而言,我认为答案是否定的,因为原始顺序在二进制中丢失了,所以……我该如何执行任务?

按声明顺序对枚举进行排序

以下是带有自定义属性的版本:

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class EnumOrderAttribute : Attribute
{
    public int Order { get; set; }
}

public static class EnumExtenstions
{
    public static IEnumerable<string> GetWithOrder(this Enum enumVal)
    {
        return enumVal.GetType().GetWithOrder();
    }
    public static IEnumerable<string> GetWithOrder(this Type type)
    {
        if (!type.IsEnum)
        {
            throw new ArgumentException("Type must be an enum");
        }
        // caching for result could be useful
        return type.GetFields()
                               .Where(field => field.IsStatic)
                               .Select(field => new
                                            {
                                                field,
                                                attribute = field.GetCustomAttribute<EnumOrderAttribute>()
                                            })
                                .Select(fieldInfo => new
                                             {
                                                 name = fieldInfo.field.Name,
                                                 order = fieldInfo.attribute != null ? fieldInfo.attribute.Order : 0
                                             })
                               .OrderBy(field => field.order)
                               .Select(field => field.name);
    }
}

用法:

public enum TestEnum
{
    [EnumOrder(Order=2)]
    Second = 1,
    [EnumOrder(Order=1)]
    First = 4,
    [EnumOrder(Order=3)]
    Third = 0
}
var names = typeof(TestEnum).GetWithOrder();
var names = TestEnum.First.GetWithOrder();

短答:

foreach(FieldInfo fi in typeof(CurrencyId).GetFields()
  .Where(fi => fi.IsStatic).OrderBy(fi => fi.MetadataToken))
    Console.WriteLine(fi.Name);

原因:

public enum EnumOrder {
    Bad = -1, Zero = 0, One = 1 }
public class ClassOrder {
    public int first;
    public int First { get { return first; } }
    public int second;
    public int Second { get { return second; } } }
private void PrintInfos<T>(string head, IEnumerable<T> list) where T: MemberInfo {
    memo.AppendText(string.Format("  {0}: ", head));
    bool first = true; foreach(var e in list) {
        if(first) first = false; else memo.AppendText(", ");
        memo.AppendText(e.Name); }
    memo.AppendText("'r'n"); }
private void ReflectionOrderTest(object sender, EventArgs e) {
    typeof(EnumOrder).GetField("One");
    typeof(ClassOrder).GetField("second");
    typeof(ClassOrder).GetProperty("Second");
    memo.AppendLine("First time order:");
    PrintInfos("Enum", typeof(EnumOrder).GetFields().Where(fi => fi.IsStatic));
    PrintInfos("Fields", typeof(ClassOrder).GetFields());
    PrintInfos("Properties", typeof(ClassOrder).GetProperties());
    PrintInfos("Members", typeof(ClassOrder).GetMembers());
    memo.AppendLine("Broken order:");
    PrintInfos("Enum", typeof(EnumOrder).GetFields().Where(fi => fi.IsStatic));
    PrintInfos("Fields", typeof(ClassOrder).GetFields());
    PrintInfos("Properties", typeof(ClassOrder).GetProperties());
    PrintInfos("Members", typeof(ClassOrder).GetMembers());
    memo.AppendLine("MetadataToken Sorted:");
    PrintInfos("Enum", typeof(EnumOrder).GetFields().Where(fi => fi.IsStatic).OrderBy(fi => fi.MetadataToken));
    PrintInfos("Fields", typeof(ClassOrder).GetFields().OrderBy(fi => fi.MetadataToken));
    PrintInfos("Properties", typeof(ClassOrder).GetProperties().OrderBy(fi => fi.MetadataToken));
    PrintInfos("Members", typeof(ClassOrder).GetMembers().OrderBy(fi => fi.MetadataToken));
}

输出:

<>之前第一次顺序:枚举:坏,零,一Fields:第一,第二属性:第一,第二成员:get_First, get_Second, ToString, Equals, GetHashCode, GetType, .ctor, Second, First, Second, First破碎的顺序:枚举:1,Bad, 0Fields:第二,第一属性:第二,第一成员:get_Second, get_First, ToString, Equals, GetHashCode, GetType, .ctor, Second, First, Second, FirstMetadataToken排序:枚举:坏,零,一Fields:第一,第二属性:第一,第二成员:first, second, ToString, Equals, GetHashCode, GetType, get_First, get_Second, .ctor, first, second之前

重要提示: MemberInfo.GetFields()自。net 2.0以来由一些缓存支持(阅读这篇关于它的好文章),并且可能不会以声明的顺序返回字段(更准确地说:编译器发出的顺序似乎保留了一个文件的顺序,但合并partial class的顺序未定义)。在stackoverflow上也有类似的问题,Marc Gravell的一条评论是:

10.2.6 Members[…]类型中成员的顺序对c#代码来说并不重要,但在与其他语言和环境。在这种情况下,排序在多个部分声明的类型中的成员是未定义的。

这样做可以解决缓存的问题:

GC.Collect();
GC.WaitForPendingFinalizers();
var fields = typeof(Whatever).GetFields();

按元数据token排序可能也有帮助。没有找到一个保证,但这应该提供了一个很好的理由为什么它应该工作:

下面的三个字节,称为记录标识符(RID),对象所指向的元数据表中行的索引token的MSB指。例如,带有value的元数据标记0x02000007表示当前作用域内TypeDef表的第7行。类似地,令牌0x0400001A指的是FieldDef中的第26行(十进制)

原始答:使用typeof(CurrencyId).GetFields(),检查FieldInfo.IsStatic(一个__value不会),然后根据需要使用FieldInfo.NameGetValue

链接到IDEONE: http://ideone.com/hnT6YL

using System;
using System.Reflection;
public class Test
{
    public enum CurrencyId {
        USD = 840,
        UAH = 980,
        RUR = 643,
        EUR = 978,
        KZT = 398,
        UNSUPPORTED = 0
    }
    public static void Main()
    {
        foreach(FieldInfo fi in typeof(CurrencyId).GetFields())
            if(fi.IsStatic) Console.WriteLine(fi.Name);
    }
}
输出:

USD
UAH
RUR
EUR
KZT
UNSUPPORTED

EDIT:订单不保证:((见注释)

GetFields方法不按特定顺序返回字段,例如字母顺序或声明顺序。你的代码不能依赖返回字段的顺序,因为该顺序不同。

这可能是。net 4.5

的解决方案
using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
[AttributeUsage(AttributeTargets.All, Inherited = false, AllowMultiple = false)]
public sealed class OrderAttribute : Attribute {
    private readonly int order_;
    public OrderAttribute(
      [CallerLineNumber] int order = 0) {
        order_ = order; }
    public int Order { get { return order_; } }
}
public class Test {
    public enum CurrencyId {
        [Order] USD = 840,
        [Order] UAH = 980,
        [Order] RUR = 643,
        [Order] EUR = 978,
        [Order] KZT = 398,
        [Order] UNSUPPORTED = 0
    }
    public static void Main() {
        foreach(FieldInfo fi in typeof(CurrencyId).GetFields()
          .Where(fi => fi.IsStatic)
          .OrderBy(fi => ((OrderAttribute)fi.GetCustomAttributes(
            typeof(OrderAttribute), true)[0]).Order))
            Console.WriteLine(fi.GetValue(null).ToString());
    }
}

就用DisplayAttribute吧。

public enum CurrencyId
{
    [Display(Order = 0)]
    USD = 840,
    [Display(Order = 1)]
    UAH = 980,
    [Display(Order = 2)]
    RUR = 643,
    [Display(Order = 3)]
    EUR = 978,
    [Display(Order = 4)]
    KZT = 398,
    [Display(Order = 5)]
    UNSUPPORTED = 0
}