简体   繁体   中英

Sort enums in declaration order

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

Is there any way to sort results of Enum.GetValues(typeof(CurrencyId)).Cast<CurrencyId>() by order they are declared in.cs file (USD, UAH, RUR, EUR, KZT, UNSUPPORTED), not by their underlying code? Personally, I believe the answer is 'no', because original order is lost in binaries, so... how can I implement the task?

Here is version with custom attribute:

[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);
    }
}

Usage:

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();

Short Answer:

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

Reason:

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));
}

Output:

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

IMPORTANT NOTES: MemberInfo.GetFields() is backed by some cache since .NET 2.0 (read this nice post about it ) and may not return the fields in declared order (more precisely: the order emited by compiler wich seems to preserve the order in one file, but order of merged partial class is undefined). Here is similar question on stackoverflow , and one comment from Marc Gravell reads:

10.2.6 Members [...] The ordering of members within a type is rarely significant to C# code, but may be significant when interfacing with other languages and environments. In these cases, the ordering of members within a type declared in multiple parts is undefined.

Doing this should overcome the problem with the cache:

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

Sorting by MetadataToken may help as well. Didn't find a guarantee, but this should provide good reasoning why it should work:

The lower three bytes, referred to as the record identifier (RID), contain the index of the row within the metadata table to which the token's MSB refers. For example, the metadata token with value 0x02000007 refers to row 7 in the TypeDef table in the current scope. Similarly, token 0x0400001A refers to row 26 (decimal) in the FieldDef table in the current scope.

ORIGINAL ANSWER: Use typeof(CurrencyId).GetFields() , check for FieldInfo.IsStatic (one __value won't be), then use FieldInfo.Name or GetValue as needed.

Link to 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);
    }
}

Output:

USD
UAH
RUR
EUR
KZT
UNSUPPORTED

EDIT: The order is not guaranteed :( (See comments)

The GetFields method does not return fields in a particular order, such as alphabetical or declaration order. Your code must not depend on the order in which fields are returned, because that order varies.

This may be solution for .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());
    }
}

Just use 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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM