2013-05-12 70 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