为什么在 .NET 5 中指定循环枚举值时枚举的顺序很重要?
Why is the order of the enums important when specifying circular enum values in .NET 5?
我正在使用枚举循环引用将一些现有枚举归结为更少的值。
它适用于我的问题,因为它适用于过渡期,旧的枚举值确实出现在历史记录中,但不会创建具有过时值的新条目。
我不是在寻找替代方法,但我偶然发现了这个奇怪的问题,枚举的顺序会以意想不到的方式影响序列化值。
我有这个枚举:
public enum CivilStatusEnum
{
None = 0,
Married = 1,
Cohabiting = Married,
Alone = 3,
DivorcedOrSeparated = Alone,
WidowOrWidower = Alone,
}
我分配了“DivorcedOrSeparated”= Alone (3)。现在,当我将枚举转换为字符串时,我得到值“DivorcedOrSeparated”。
Console.PrintLine(CivilStatusEnum.Alone.ToString());
输出:
DivorcedOrSeparated
我有一个示例代码,其中包含测试及其预期结果。如您所见,测试失败了。但是如果我改变枚举的顺序,测试就会解析。
[Theory]
[InlineData(CivilStatusEnum.Alone, "Alone")]
[InlineData(CivilStatusEnum.DivorcedOrSeparated, "Alone")]
[InlineData(CivilStatusEnum.WidowOrWidower, "Alone")]
[InlineData(CivilStatusEnum.None, "None")]
[InlineData(CivilStatusEnum.Married, "Married")]
[InlineData(CivilStatusEnum.Cohabiting, "Married")]
public void Test(CivilStatusEnum input, string expected)
{
var text = input.ToString();
text.Should().Be(expected);
}
我似乎找不到一个合理的解释来解释为什么顺序对 tostring 和序列化很重要。
这是 .NET 5 中的错误,还是我遗漏了什么?
如果此行为是故意的,它如何确定哪个枚举名称将是 tostring 的输出?
谢谢:)
Enum.ToString
执行二进制搜索。
的确,ToString calls InternalFormat, which calls GetEnumName。该方法在数组 return 中执行二进制搜索 EnumInfo.Values。
我假设数组是按照基础值的递增顺序填充的(否则二进制搜索将不起作用),如果它们相等,则按照在源代码中声明的值的顺序填充。这使得搜索结果取决于声明的顺序。
为了说明这种二分查找的效果,请考虑以下两个 enum
定义:
enum Test1 { A = 0, B = 0, C = 0 }
enum Test2 { A = 0, B = 0, C = 0, D = 0, E = 0 }
Test1.A.ToString()
的结果是什么?请注意,Test1.A
的值为 0
。二分查找首先会考虑列表中间的元素,即B
,值为0
。该值等于我们正在搜索的值,因此 Test1.A.ToString()
returns "B"
。如果找到的值高于正在搜索的值,则搜索将在列表的下半部分继续。如果找到的值低于正在搜索的值,搜索将在列表的上半部分继续。
对于枚举中的所有常量也是如此,因为它们都具有相同的值。所以,Test1.C.ToString()
等同于 return "B"
.
同理,Test2.A.ToString()
returns"C"
,符合预期。
但是请注意,虽然此行为在当前版本的 .NET 中似乎是 可预测的 ,但它是 未定义的 ,并且可能会改变在未来的版本中。
这不是 .NET 5 中的错误。毕竟,以下两个条件都为真是不可能的:
CivilStatusEnum.Alone.ToString() == "Alone"
CivilStatusEnum.DivorcedOrSeparated.ToString() == "DivorcedOrSeparated"
原因当然是CivilStatusEnum.Alone == CivilStatusEnum.DivorcedOrSeparated
.
以下是 the documentation 对此的看法:
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.
我知道你说过你不是在寻找替代方法,但另一种方法是使用标志来避免此类问题:
[Flags]
public enum CivilStatusEnum
{
None = 0,
Married = 1,
Cohabiting = 3, //Married | 2
Alone = 4,
DivorcedOrSeparated = 12, //Alone | 8
WidowOrWidower = 20, //Alone | 16
}
然后ToString
会给出正确的答案,你可以用下面的代码检查某人是已婚还是单身:
bool IsMarried(CivilStatusEnum e){
return ((int)e&1) == 1;
}
bool IsAlone(CivilStatusEnum e){
return ((int)e&4) == 4;
}
我正在使用枚举循环引用将一些现有枚举归结为更少的值。 它适用于我的问题,因为它适用于过渡期,旧的枚举值确实出现在历史记录中,但不会创建具有过时值的新条目。
我不是在寻找替代方法,但我偶然发现了这个奇怪的问题,枚举的顺序会以意想不到的方式影响序列化值。
我有这个枚举:
public enum CivilStatusEnum
{
None = 0,
Married = 1,
Cohabiting = Married,
Alone = 3,
DivorcedOrSeparated = Alone,
WidowOrWidower = Alone,
}
我分配了“DivorcedOrSeparated”= Alone (3)。现在,当我将枚举转换为字符串时,我得到值“DivorcedOrSeparated”。
Console.PrintLine(CivilStatusEnum.Alone.ToString());
输出:
DivorcedOrSeparated
我有一个示例代码,其中包含测试及其预期结果。如您所见,测试失败了。但是如果我改变枚举的顺序,测试就会解析。
[Theory]
[InlineData(CivilStatusEnum.Alone, "Alone")]
[InlineData(CivilStatusEnum.DivorcedOrSeparated, "Alone")]
[InlineData(CivilStatusEnum.WidowOrWidower, "Alone")]
[InlineData(CivilStatusEnum.None, "None")]
[InlineData(CivilStatusEnum.Married, "Married")]
[InlineData(CivilStatusEnum.Cohabiting, "Married")]
public void Test(CivilStatusEnum input, string expected)
{
var text = input.ToString();
text.Should().Be(expected);
}
我似乎找不到一个合理的解释来解释为什么顺序对 tostring 和序列化很重要。
这是 .NET 5 中的错误,还是我遗漏了什么? 如果此行为是故意的,它如何确定哪个枚举名称将是 tostring 的输出?
谢谢:)
Enum.ToString
执行二进制搜索。
的确,ToString calls InternalFormat, which calls GetEnumName。该方法在数组 return 中执行二进制搜索 EnumInfo.Values。
我假设数组是按照基础值的递增顺序填充的(否则二进制搜索将不起作用),如果它们相等,则按照在源代码中声明的值的顺序填充。这使得搜索结果取决于声明的顺序。
为了说明这种二分查找的效果,请考虑以下两个 enum
定义:
enum Test1 { A = 0, B = 0, C = 0 }
enum Test2 { A = 0, B = 0, C = 0, D = 0, E = 0 }
Test1.A.ToString()
的结果是什么?请注意,Test1.A
的值为 0
。二分查找首先会考虑列表中间的元素,即B
,值为0
。该值等于我们正在搜索的值,因此 Test1.A.ToString()
returns "B"
。如果找到的值高于正在搜索的值,则搜索将在列表的下半部分继续。如果找到的值低于正在搜索的值,搜索将在列表的上半部分继续。
对于枚举中的所有常量也是如此,因为它们都具有相同的值。所以,Test1.C.ToString()
等同于 return "B"
.
同理,Test2.A.ToString()
returns"C"
,符合预期。
但是请注意,虽然此行为在当前版本的 .NET 中似乎是 可预测的 ,但它是 未定义的 ,并且可能会改变在未来的版本中。
这不是 .NET 5 中的错误。毕竟,以下两个条件都为真是不可能的:
CivilStatusEnum.Alone.ToString() == "Alone"
CivilStatusEnum.DivorcedOrSeparated.ToString() == "DivorcedOrSeparated"
原因当然是CivilStatusEnum.Alone == CivilStatusEnum.DivorcedOrSeparated
.
以下是 the documentation 对此的看法:
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.
我知道你说过你不是在寻找替代方法,但另一种方法是使用标志来避免此类问题:
[Flags]
public enum CivilStatusEnum
{
None = 0,
Married = 1,
Cohabiting = 3, //Married | 2
Alone = 4,
DivorcedOrSeparated = 12, //Alone | 8
WidowOrWidower = 20, //Alone | 16
}
然后ToString
会给出正确的答案,你可以用下面的代码检查某人是已婚还是单身:
bool IsMarried(CivilStatusEnum e){
return ((int)e&1) == 1;
}
bool IsAlone(CivilStatusEnum e){
return ((int)e&4) == 4;
}