2013-05-12 98 views
3

的考虑下面的枚举:优先枚举名

[Flags] 
public enum Intervals 
{ 
    Root = PerfectUnison, 
    Unison = PerfectUnison, 
    PerfectUnison = 1 << 0, 
    AugmentedUnison = MinorSecond, 

    MinorSecond = 1 << 1, 
    Second = MajorSecond, 
    MajorSecond = 1 << 2, 
    AugmentedSecond = MinorThird, 

    MinorThird = 1 << 3, 
    Third = MajorThird, 
    MajorThird = 1 << 4, 
    AugmentedThird = PerfectFourth, 
    DoubleAugmentedThird = Triton, 

    DiminishedFourth = MajorThird, 
    Fourth = PerfectFourth, 
    PerfectFourth = 1 << 5, 
    AugmentedFourth = Triton, 
    DoubleAugmentedFourth = PerfectFifth, 

    Triton = 1 << 6, 

    //...Removed for brevity, see link to code bellow 
} 

我想这个简单的测试:

static void Main(string[] args) 
{ 
    var values = Enum.GetValues(typeof(Intervals)); 
    foreach (var value in values) 
    { 
    Console.WriteLine(value); 
    } 
} 

这里是输出:

PerfectUnison,PerfectUnison, PerfectUnison,AugmentedUnison,AugmentedUnison,Second,Second,MinorThird,MinorThird,DiminishedFourth,DiminishedFourth,DiminishedFourth,AugmentedThird,AugmentedThi RD,AugmentedThird,AugmentedThird,DoubleDiminishedSixth,DoubleDiminishedSixth等

虽然我想选择用于相同的值枚举名称为以下序列组成:

根,MinorSecond,第二,MinorThird,第三,四,海卫,五,MinorSixth,第六,MinorSeventh,七,八度,MinorNinth,第九,第十,第十一,MajorEleventh,十三

一个很好的再现也将是Enum.GetNames。我希望上述组的名称应该总是在它们的值匹配名称之前。

我基本上是在寻找每个值的枚举名称优先级/优先级规则的文档。

你可以在这里使用代码:http://rextester.com/EJOWK87857

更新

我现在正在研究反编译Enum.GetNames。看起来它使用反射。那么问题是,“如何控制反射场的顺序?”。

+0

AFAIK,这是不雷尔可能。例如,[Enum.GetName](http://msdn.microsoft.com/en-us/library/system.enum.getname.aspx)方法明确提到您不应该依赖于检索名称的一致结果具有重复值的枚举。您可以使用'Enum.GetNames'(它会根据需要返回值),然后使用这些名称依次使用[Enum.Parse](http://msdn.microsoft.com/en-us /library/essfb559%28v=vs.90%29.aspx)method.EDIT:但是'Parse'的结果仍然是重复的,而不是原件;不知道这是否有帮助。 – 2013-05-12 02:25:11

+0

@ChrisSinclair我现在正在研究反编译的'Enum.GetNames'。看起来它使用反射。那么问题是,“如何控制反射场的顺序?”。 – Shimmy 2013-05-12 02:48:18

回答

5

不使用元数据,这是不可能的,因为编译器可能会为每个枚举成员分配常量值。检查编译IL示出了当代码被编译的分配信息丢失:

.field public static literal valuetype .../Intervals Unison = int32(1)  
.field public static literal valuetype .../Intervals PerfectUnison = int32(1) 
.field public static literal valuetype .../Intervals AugmentedUnison = int32(2) 
... 

由于当源编译此信息丢失(或,至少,不保证可用),这将不可能在运行时根据赋值来分配优先级规则。这种限制是与文档Enum.ToString(),其中规定,如果有多个名称与相同的值关联,选择的成员是不确定的一致:

如果多个枚举成员具有相同的基础值,并尝试检索字符串表示枚举成员的名称基于其基础值,您的代码不应该对该方法返回的名称做任何假设。

这就是说,一种可能的解决方法可能是将属性值分配给被认为是赋值优先级的枚举值。例如:

[AttributeUsage(AttributeTargets.Field)] 
class PriorityAttribute : Attribute { } 
[Flags] 
public enum Intervals 
{ 
    Root = PerfectUnison, 
    Unison = PerfectUnison, 
    [Priority] 
    PerfectUnison = 1 << 0, 
    AugmentedUnison = MinorSecond, 

    [Priority] 
    MinorSecond = 1 << 1, 
    Second = MajorSecond, 
    [Priority] 
    MajorSecond = 1 << 2, 
    AugmentedSecond = MinorThird, 
    ... 

由于属性信息与在运行时枚举值相关联,标记枚举的名称可以在运行时访问:

typeof(Intervals) 
    .GetFields() 
    .Where(a => a.GetCustomAttributes(typeof(PriorityAttribute), false).Length > 0) 
    .Select(a => a.Name)) 

同样,你可以写一模拟Enum.GetName到与定义的属性只返回名称(例如,GetPriorityName(typeof(Intervals), 1)总是返回PerfectUnison

static string GetPriorityName(Type enumType, object v) 
{ 
    Type ut = Enum.GetUnderlyingType(enumType); 
    var pty = enumType.GetFields() 
     .Where(
      a => a.IsLiteral 
      && a.GetRawConstantValue().Equals(v) 
      && a.GetCustomAttributes(typeof(PriorityAttribute), false).Length > 0 
      ) 
     .FirstOrDefault(); 
    if (pty == null) 
     return Enum.GetName(enumType, v); // default to standard if no priority defined 
    return pty.Name; 
} 
+0

我想我会删除所有重复的值,只留下'默认'的值。因为我想要的是'ToString'来返回这些值。一个属性不会给我任何东西。 – Shimmy 2013-05-12 02:56:39

+0

为什么不创建两个单独的枚举,其中一个具有所有值,另一个(第二个)仅包含“默认”项。你可以从第一个到第二个,并且在第二个枚举中调用'ToString()'将只显示默认值。 – drf 2013-05-12 02:59:46

+0

好主意 - 糟糕的设计习惯。 – Shimmy 2013-05-12 03:02:29