【问题标题】:Precedence of enum names枚举名称的优先级
【发布时间】:2013-05-06 09:10:42
【问题描述】:

给定以下枚举:

[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 等。

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

根音,MinorSecond,Second,MinorThird,Third,Fourth,Triton,Fifth,MinorSixth,Sixth,MinorSeventh,Seventh,Octave,MinorNinth,Ninth,Tenth,11th,MajorEleventh,13

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

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

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

更新

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

【问题讨论】:

  • AFAIK,这真的不可能。例如,Enum.GetName 方法特别提到,在检索具有重复值的枚举名称时,您不应该依赖一致的结果。您可以使用Enum.GetNames(它确实返回您想要的值)并使用这些名称然后使用Enum.Parse 方法按顺序检索值。编辑:但Parse 的结果仍然是重复的,而不是原件;不确定这是否有帮助。
  • @ChrisSinclair 我现在正在研究反编译的Enum.GetNames。看起来它使用反射。那么问题来了,“如何控制反射场的顺序?”。

标签: c# enums


【解决方案1】:

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

【讨论】:

  • 我想我会删除所有重复的值,只保留“默认”值。因为我想要的是 ToString 返回这些值。属性不会给我任何东西。
  • 为什么不创建两个单独的枚举,一个包含所有值,一个(第二个)仅包含“默认”项。您可以从第一个转换到第二个,在第二个枚举上调用 ToString() 只会显示默认值。
  • 好主意 - 糟糕的设计习惯。
猜你喜欢
  • 2015-03-07
  • 1970-01-01
  • 1970-01-01
  • 2014-11-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-12
  • 1970-01-01
相关资源
最近更新 更多