Reputation: 104692
Given the following 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
}
I'm trying this simple test:
static void Main(string[] args)
{
var values = Enum.GetValues(typeof(Intervals));
foreach (var value in values)
{
Console.WriteLine(value);
}
}
And here is the output:
PerfectUnison, PerfectUnison, PerfectUnison, AugmentedUnison, AugmentedUnison, Second, Second, MinorThird, MinorThird, DiminishedFourth, DiminishedFourth, DiminishedFourth, AugmentedThird, AugmentedThird, AugmentedThird, AugmentedThird, DoubleDiminishedSixth, DoubleDiminishedSixth etc.
While I want the enum names selected for identical values to be of the following sequence:
Root, MinorSecond, Second, MinorThird, Third, Fourth, Triton, Fifth, MinorSixth, Sixth, MinorSeventh, Seventh, Octave, MinorNinth, Ninth, Tenth, Eleventh, MajorEleventh, Thirteen
A good reproduction would also be Enum.GetNames
. I want the names of the above group should always precede their value-matching names.
I'm basically looking for a documentation of the rules of precedence/priority of enum names per value.
You can play around with the code here: http://rextester.com/EJOWK87857.
Update
I'm now looking into decompiled Enum.GetNames
. Looks like it uses reflection. So the question is then, "How to control the order of reflected fields?".
Upvotes: 3
Views: 1138
Reputation: 8699
Without using metadata, this is not possible since the compiler may assign the constant value to each enum member. Examining the compiled IL shows that the assignment information is lost when the code is compiled:
.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)
...
Since this information is lost when the source is compiled (or, at least, is not guaranteed to be available), it would not be possible to assign priority rules based on assignment at runtime. This limitation is consistent with the documentation for Enum.ToString()
, which states that if multiple names are associated with the same value, the member chosen is nondeterministic:
If multiple enumeration members have the same underlying value and you attempt to retrieve the string representation of an enumeration member's name based on its underlying value, your code should not make any assumptions about which name the method will return.
This said, a possible workaround may be to assign attribute values to the enum values that are deemed to be a priority on assignment. For instance:
[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,
...
Since the attribute information is associated with the enum values at runtime, the marked enumeration names can be accessed at runtime:
typeof(Intervals)
.GetFields()
.Where(a => a.GetCustomAttributes(typeof(PriorityAttribute), false).Length > 0)
.Select(a => a.Name))
Likewise, you can write an analogue to Enum.GetName
to return only the names with the attribute defined (e.g., GetPriorityName(typeof(Intervals), 1)
will always return 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;
}
Upvotes: 5