枚举名称的优先级

本文关键字:优先级 枚举 | 更新日期: 2023-09-27 18:13:14

给定以下enum:

[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, minor - third, minor - third, DiminishedFourth, AugmentedThird, AugmentedThird, AugmentedThird, DoubleDiminishedSixth, DoubleDiminishedSixth等

而我希望为相同值选择的enum名称具有以下顺序:

大调,小调二,二,小调三,三,四,三,五,小调六,六,小调七,七,八度,小调九,九,十,十一,大调七,十三

一个好的复制品也可能是Enum.GetNames。我希望上述组的名称总是在它们的值匹配名称之前。

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

您可以使用下面的代码:http://rextester.com/EJOWK87857.

我现在正在查看反编译的Enum.GetNames。看起来它使用了反射。那么问题来了,"如何控制反射字段的顺序?"

枚举名称的优先级

如果不使用元数据,这是不可能的,因为编译器可能会将常量值赋给每个enum成员。检查编译后的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;
}