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、AugmentedThird、AugmentedThird、AugmentedThird、DoubleDiminishedSixth、DoubleDiminishedSixth 等。

虽然我希望为相同值选择的枚举名称具有以下顺序:

Root, MinorSecond, Second, MinorThird, 第三, 第四, Triton, 第五, MinorSixth, Sixth, MinorSeventh, 第七, 八度, MinorNinth, 第九, 第十, 十一, MajorEleventh, 十三

一个好的复制也将是Enum.GetNames。我希望上述组的名称应始终位于其值匹配名称之前。

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

您可以在此处使用代码:http ://rextester.com/EJOWK87857 。

更新

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

4

1 回答 1

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

同样,您可以编写一个类似 toEnum.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;
}
于 2013-05-12T02:53:33.477 回答