11
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?

Denis
  • 3,653
  • 4
  • 30
  • 43
  • http://stackoverflow.com/questions/6819348/enum-getnames-results-in-unexpected-order-with-negative-enum-constants – Habib Aug 05 '14 at 19:59
  • 4
    Create a custom attribute with an "Order" property, and decorate each enum member with the attribute. – Michael Liu Aug 05 '14 at 20:00
  • Can you explain *why* you need them in source order? Because otherwise I'm going to go with "make a list with them in the order you want, then iterate over the list". – Rawling Aug 06 '14 at 07:03

3 Answers3

16

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();
  • Well, having to use such attribute, I would rather create one specifying the whole order (actually, I already have one created in my custom library, but I am often using it as `[Props("One,Two,...")]` meaning `One,Two + find the rest`). Here is my code I use for quite a long time: http://pastebin.com/KpcHZSy3 – firda Aug 05 '14 at 21:57
  • 1
    @firda hm.. In my opinion it is not good idea. I wouldn't store enum values / names twice - in definition and inside attribute. What if enum member name / value would change and forogt to update inside attribute? As for me, `CallerLineNumber` is better idea :) –  Aug 06 '14 at 06:41
  • 1
    Sure, [CallerLineNumber] seems to be the best solution. Forgetting to update the PropsAttribute could produce problems, I agree. See my updated answer, I'd like to find a solution for .NET 3.5 as well (CallerLineNumber is .NET 4.5+) – firda Aug 06 '14 at 06:51
  • Excellent answer! I have developed similar one after @Michael Liu comment. Maybe my is something slower but makes possible to ignore 'UNSUPPORTED' value. Example: http://pastebin.com/Zffru73R – Denis Aug 06 '14 at 08:54
  • @Denis Glad it helped :) Remember to cache values got via reflection (f. ex. in static variable - f.e. values of enum and attribute instance) - it could make solution many faster. –  Aug 06 '14 at 08:58
  • @Denis Question: if you're happy to have some extra code that you have to keep up to date alongside the enum declaration (in this case, the added attributes), why go to all this effort with reflection rather than just *keeping a static list of the enums in the order you want them*? – Rawling Aug 06 '14 at 10:53
  • @Rawling because I do not have to add values twice. If I add new value to enum and forget to add Attribute - my code will put it at the end of list. – Denis Aug 06 '14 at 23:07
  • @Denis You can do that with the list, two LINQ calls and `Enum.GetValues`. – Rawling Aug 07 '14 at 07:12
  • There's also the option of using the `System.ComponentModel.DataAnnotations.DisplayAttribute`, especially if you already use it for giving friendly display names to your enums. You just go `[Display(Order = 0)]` and extract the attribute value the same as above. – Marius Bughiu Oct 12 '16 at 11:46
7

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 thisshould 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());
    }
}
Community
  • 1
  • 1
firda
  • 3,268
  • 17
  • 30
  • 2
    As CodeCaster pointed out, "_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._" (See http://msdn.microsoft.com/en-us/library/ch9714z3(v=vs.110).aspx.) Some people suggest sorting fields by `MemberInfo.MetadataToken`, but there seems to be **no guarantee** that it always works. – AlexD Aug 05 '14 at 20:49
  • Hmm, didn't know that. I am using the order in one of my applications and it always returned the declaration order (if no `partial` was involved - in this case the order of files is unspecified, but declaration order within same file was always preserved). – firda Aug 05 '14 at 21:00
  • I have found this: http://stackoverflow.com/questions/9062235/get-properties-in-order-of-declaration-using-reflection suggesting to use `[CallerLineNumber]` attribute. Looks good. – firda Aug 05 '14 at 21:12
  • @frida Yes, it can be a solution, perhaps. But it still requires some decorating of the fields and hence access to the sources. – AlexD Aug 05 '14 at 21:33
  • @AlexD: I cannot use it :( I am forced to use .NET 3.5 (sometimes even .NET 2.0 on one old server). I'll probably create some custom code generator for that (I am using the order in my custom PropertyGrid-like component which is the core for 'Configuration Editor'). Too bad :( – firda Aug 05 '14 at 21:51
  • @firda: If you have a fixed version (like 3.5 or 2.0), you can probably rely on the order not changing. The real issue is that a future compiler or runtime may return the values in a different order. – Gabe Aug 05 '14 at 22:16
  • @Gabe: Good news. .NET 3.5 (actually runtime 2.0) is almost *dead*, so, I hope I can suppose that customers are safe for now... but have to deal with that in the future. – firda Aug 05 '14 at 22:20
  • @Gabe But could we guarantee that older compilers _always_ behave the way we want? It is theoretically possible that after modification and re-compilation the order of fields changes. – AlexD Aug 05 '14 at 22:21
  • @AlexD: From what I have seen so far, the order of fields specified in one file is preserved. Why would they break it? How about binary serializers? Do they always have to store signature of the field? That's ridiculous!! – firda Aug 06 '14 at 05:33
  • @AlexD: Answer updated - .NET 2.0 introduced reflection cache :( Still searching for guarantee that sorting by MetadataToken solves this. Any help? – firda Aug 06 '14 at 07:14
  • @frida I found no explicit guarantee so far. Moreover, I'm not sure if C#-standard compliant compiler is obliged to store the initial order information in any way. – AlexD Aug 06 '14 at 09:20
  • @AlexD: Well, custom code generator (or compiler) is the only way for .NET prior 4.5 if you don't want to manage the order manually. For now, I am gonna fix my engine with MetadataToken-Sort and plan for furute code generator (metedata generator, serializer generator). – firda Aug 06 '14 at 10:10
2

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
}
tsu1980
  • 2,472
  • 1
  • 25
  • 13