【问题标题】:Why can I assign 0.0 to enumeration values, but not 1.0为什么我可以将 0.0 分配给枚举值,但不能分配 1.0
【发布时间】:2014-07-08 20:57:10
【问题描述】:

出于好奇:为什么我可以将 0.0 分配给枚举类型的变量,而不是 1.0?看看下面的代码:

public enum Foo
{
    Bar,
    Baz
}

class Program
{
    static void Main()
    {
        Foo value1 = 0.0;
        Foo value2 = 1.0;   // This line does not compile
        Foo value3 = 4.2;   // This line does not compile
    }
}

我认为数字类型和枚举值之间的转换只允许通过强制转换?那就是我可以写 Foo value2 = (Foo) 1.0; 以便 Main 中的第 2 行可以编译。为什么 C# 中的值 0.0 存在异常?

【问题讨论】:

标签: c# enums floating-point literals


【解决方案1】:

乔恩的回答是正确的。我将添加以下几点。

  • 我造成了这个愚蠢而令人尴尬的错误。非常抱歉。

  • 这个错误是因为我误解了编译器中“表达式为零”谓词的语义;我相信它只检查整数零相等,而实际上它正在检查更多“这是这种类型的默认值吗?”事实上,在早期版本的错误中,实际上可以将任何类型的默认值分配给枚举!它现在只是数字的默认值。 (课程:仔细命名您的辅助谓词。)

  • 我试图实现但我搞砸的行为实际上是一个稍微不同的错误的解决方法。你可以在这里阅读整个可怕的故事:https://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-onehttps://docs.microsoft.com/en-us/archive/blogs/ericlippert/the-root-of-all-evil-part-two (经验教训:在修复旧问题的同时引入新的更严重的错误非常容易。)

  • C# 团队决定保留这种有缺陷的行为,而不是修复它,因为破坏现有代码而没有令人信服的好处的风险太高了。 (教训:第一次做对!)

  • 我在 Roslyn 中编写的用于保留此行为的代码可以在 https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Binder/Semantics/Conversions/ConversionsBase.cs 中的方法 IsConstantNumericZero 中找到——请参阅它以了解有关 Roslyn 行为的详细信息。我在 Conversions 目录中编写了几乎所有的代码;我鼓励您阅读所有内容,因为有许多有趣的事实表明 C# 如何与 cmets 中的规范背道而驰。我用 SPEC VIOLATION 装饰了每一个,以便于找到它们。

还有一个有趣的地方:C# 还允许在枚举初始化器中使用 任何枚举值,而不管其是否为零:

enum E { A = 1 }
enum F { B = E.A }  // ???

规范对于这是否合法有些模糊,但同样,由于这在编译器中已经存在很长时间了,新的编译器很可能会保持这种行为。

【讨论】:

  • 这真的很酷,我终于可以看到你写的代码了。 Roslyn 源代码是开源的,这真是太棒了。现在我完全明白存在不提供更改历史记录的正当理由(技术/法律),但如果看到更改历史记录以了解代码是如何演变的,那就太棒了。
  • The C# team decided to enshrine this buggy behaviour rather than fixing it because the risk of breaking existing code for no compelling benefit was too high. 我认为不会有很多人依赖这种行为,而修复这些奇怪的问题可能会更好。但是,它也不会造成太大的伤害(实施规范的项目除外)。
  • @Aidiakapi:确实,受影响的人数应该很少;它不为零。 C# 团队非常重视重大变更。 很容易说最好修复它;您不必与愤怒的客户打交道,他们会打电话给您的副总裁抱怨您的微不足道的更改并没有增加任何好处,从而将他们的系统集成延迟了一天。
  • 情况变得更糟了。所有此类重大更改(理想情况下)都将列在 Microsoft 的框架迁移指南中。这个列表越长,用户迁移他们的应用程序就越犹豫。因此,即使是很小的中断更改也会导致: 1. 少量应用程序中断。 2. 少数用户拒绝升级(即使问题不影响他们)。 3. 少数用户浪费资源评估重大变更是否影响他们。 4. #1、#2 和 #3 的用户向其他人投诉。
  • @EricLippert 如果“规范有些模糊”,那么更新规范是否有意义? (真正的问题!)
【解决方案2】:

enum 确实旨在(在所有支持它的语言中)成为一种使用有意义且唯一的字符串(标签)而不是数值的方式。因此,在您的示例中,您应该只在处理 Foo 枚举数据类型时使用 BarBaz。您永远不应该使用(比较或分配)整数,即使许多编译器会让您侥幸逃脱(枚举在内部通常是整数),在这种情况下,0.0 会被粗心对待编译器为 0。

从概念上讲,可以将整数 n 添加到枚举值,以进一步获取 n 值,或者取 val2 -val1 看看它们相距多远,但除非语言规范明确允许这样做,否则我会避免它。 (以你可以使用它的方式将枚举值想象成一个 C 指针。)没有理由不能用浮点数实现枚举,并且它们之间有一个固定的增量,但我还没有听说过这可以用任何语言完成。

【讨论】:

  • 我知道我不应该在 C# 中那样使用枚举 - 但我发现了这个脑筋急转弯,想知道为什么 0.0 有效,但 1.0 无效。我知道它必须与 C# 编译器有关,因为您可以看到 Foo v1 = 0.0; 的 IL 代码与 Foo v2 = Foo.Bar 的相同。
【解决方案3】:

这是一个可以使用 0.0.0 的错误。编译器将所有值为 0 的常量表达式隐式视为 0。

现在,编译器正确根据 C# 5 规范的第 6.1.3 节允许从常量 int 表达式 0 到您的枚举的隐式转换:

隐式枚举转换允许将十进制整数文字 0 转换为任何枚举类型以及任何其基础类型为枚举类型的可空类型。在后一种情况下,通过转换为基础枚举类型并包装结果来评估转换(第 4.1.10 节)。

我之前曾与 C# 团队讨论过这个问题:他们希望将 意外 从 0.0(实际上是 0.0m 和 0.0f)转换为枚举值,但不幸的是我认为它破坏了太多代码——即使它一开始就不应该被允许。

Mono mcs 编译器禁止所有这些浮点转换,尽管它确实允许:

const int Zero = 0;
...

SomeEnum x = Zero;

尽管Zero 是一个常量表达式,但不是十进制整数文字。

如果将来看到 C# 规范发生变化以允许任何值为 0 的整数常量表达式(即模仿 mcs),我不会感到惊讶,但我不希望浮点转换永远存在正式是正确的。 (当然,我之前对 C# 未来的预测是错误的……)

【讨论】:

  • 根据规范,它只意味着 literal 0。因此它应该拒绝 1-1 - 一个值为 0 的常量 int 表达式。但正如您所观察到的,编译器与此处的规范不符。
  • it broke too much code - 真的很难想象有什么理由写这样的代码。
  • @ObsidianPhoenix:我不确定你的意思。它完全等同于:SomeEnum x = (SomeEnum) 0;。无论有没有命名的零值都是如此。
  • @ObsidianPhoenix:不,因为 Test.Foo 的值是 1,而不是 0...再次,这与您编写 Test v1 = (Test) 0; 完全相同 - 而且这种行为适用于 任何值,它不是枚举中的命名值。
  • @JonSkeet 会在罗斯林修复吗?
【解决方案4】:

C# 中的枚举根据定义是整数值。为了保持一致性,C# 不应该接受这些赋值中的任何一个,但 0.0 被默默地视为整数 0。这可能是对 C 的保留,其中文字 0 被特殊处理,并且基本上可以采用任何给定的类型——整数、浮点数、空指针……你可以命名它。

【讨论】:

  • 问题是为什么?如果您转到IL - 它会将整数值推入堆栈IL_0001: ldc.i4.0
  • @IlyaIvanov 查看更新。但老实说,答案是“没有充分的理由”。
  • 我认为这是其中一种情况,如果您查看 C# spec,这是不合法的,但是如果您查看 MS 生成的任何 C# 编译器,它会这样做。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-08
  • 2021-10-21
  • 2011-10-11
  • 1970-01-01
  • 2023-04-07
  • 1970-01-01
相关资源
最近更新 更多