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

Den*_*nis 9 c# enums

public enum CurrencyId
{
    USD = 840,
    UAH = 980,
    RUR = 643,
    EUR = 978,
    KZT = 398,
    UNSUPPORTED = 0
}
Run Code Online (Sandbox Code Playgroud)

有没有办法Enum.GetValues(typeof(CurrencyId)).Cast<CurrencyId>()按顺序对结果进行排序?它们是在.cs文件(USD,UAH,RUR,EUR,KZT,UNSUPPORTED)中声明的,而不是它们的底层代码?就个人而言,我认为答案是'不',因为原始订单在二进制文件中丢失,所以...我该如何实现任务?

小智 13

这是具有自定义属性的版本:

[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);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

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();
Run Code Online (Sandbox Code Playgroud)


fir*_*rda 5

简答:

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

原因:

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));
}
Run Code Online (Sandbox Code Playgroud)

输出:

First time order:
  Enum: Bad, Zero, One
  Fields: first, second
  Properties: First, Second
  Members: get_First, get_Second, ToString, Equals, GetHashCode, GetType, .ctor, Second, First, second, first
Broken order:
  Enum: One, Bad, Zero
  Fields: second, first
  Properties: Second, First
  Members: get_Second, get_First, ToString, Equals, GetHashCode, GetType, .ctor, Second, First, second, first
MetadataToken Sorted:
  Enum: Bad, Zero, One
  Fields: first, second
  Properties: First, Second
  Members: 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成员[...]类型中成员的排序对C#代码来说很少有意义,但在与其他语言和环境交互时可能很重要.在这些情况下,未定义在多个部分中声明的类型内的成员的顺序.

这样做应该克服缓存的问题:

GC.Collect();
GC.WaitForPendingFinalizers();
var fields = typeof(Whatever).GetFields();
Run Code Online (Sandbox Code Playgroud)

按MetadataToken排序也可能有所帮助.没有找到一个保证,但这个应该提供良好的理由,为什么它应该工作:

较低的三个字节(称为记录标识符(RID))包含令牌的MSB引用的元数据表中的行的索引.例如,值为0x02000007的元数据标记引用当前范围中TypeDef表中的第7行.类似地,令牌0x0400001A引用当前作用域中FieldDef表中的第26行(十进制).

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

链接到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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

USD
UAH
RUR
EUR
KZT
UNSUPPORTED
Run Code Online (Sandbox Code Playgroud)

编辑: 订单不保证:((见评论)

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 正如CodeCaster指出的那样,"_ GetFields方法不会按特定顺序返回字段,例如字母顺序或声明顺序.您的代码不能依赖于返回字段的顺序,因为该顺序会有所不同."(参见http: //msdn.microsoft.com/en-us/library/ch9714z3(v=vs.110).aspx.)有些人建议用`MemberInfo.MetadataToken`排序字段,但似乎**不保证**它总是有效的. (2认同)