如何使用底层值定义枚举常量

7

这个C#代码的F#等价代码是什么:

const MyEnum None = (MyEnum)1;

这个不起作用:

[<Literal>]
let None : MyEnum = enum 1 //ERROR: not a valid constant expression

尽管有趣的是,在属性构造函数中可以使用它:
[<MyAttribute(enum 1)>]
type T = class end

这个不一致看起来很奇怪。

更新

这在v3.1中已经修复,并且按预期工作。


是的,这很奇怪,如果在未来的F#版本中修复了它,我也不会感到惊讶。 - Tomas Petricek
2个回答

2
我认为观察到的编译器行为是有意的,并且完全符合由F#语言规范10.2.2:定义的具有[<Literal>]属性的值的限制。
引用如下:

右侧表达式必须是文字常量表达式,由以下内容组成:

  • 简单常量表达式,除了()、本机整数字面量、无符号本机整数字面量、字节数组字面量、BigInteger 字面量和用户定义的数字字面量之外。 - 或 -
  • 对另一个文字的引用
考虑
type MyEnum =
| Case1 = 1
| Case2 = 2

那么

[<Literal>]
let Valid: MyEnum = MyEnum.Case1 // Literal enumeration case on the right

将其翻译成中文为:"乐意编译,但是"。
[<Literal>]
let Invalid: MyEnum = enum<MyEnum>(1) // Expression on the right
                                      // generating constant value, which
                                      // potentially might be completely off
                                      // legit MyEnum cases

不会,尽管在没有 [<Literal>] 上下文的情况下,这两个语句将编译为完全相同的 IL。
假设 [<Literal>] 属性是 F# 中使 C# const 等效的唯一方法,则定义枚举字面值的唯一选项是在 let 的右侧使用适当类型的字面枚举情况。

我觉得令人困惑的是它在属性构造函数中是有效的。我希望属性参数和字面值有类似的规则。F#规范对此没有明确规定,但C#规范说明一个属性参数(E)必须是:常量、System.Type对象或E的一维数组......然而,在这种情况下,enum函数也可以工作。 - Daniel
简而言之,在属性构造函数中,enum 1 被视为常量,但在定义字面值时不是。这是一个错误吗? - Daniel
也许值得注意的是,enum 仅仅通过 EnumOfValue 转发到 unboxPrim(定义如下):let inline unboxPrim<'T>(x:obj) = (# "unbox.any !0" type ('T) x : 'T #) 这绝对是一个神奇的函数。 - Daniel

1
这种差异是由于C#中的(MyEnum)0确实是一个字面量,但是F#中的enum是一个类型为int32 -> 'T的函数引起的。
我相信对于F#团队来说,添加这种构造的特殊处理并不难,但遗憾的是目前还没有。
尽管如此,仍有一种方法可以实现您需要的功能,但只适用于0值:
type MyEnum =
    | None = 0
    | Foo = 1

[<Literal>]
let X = MyEnum()

有趣。我随意选择了0作为我的示例。在属性中使用enum是有意义的,但在常量中则没有多大意义。 - Daniel
@Daniel 你有例子需要 Literal 的数字值,但不能使用枚举值吗? - Be Brave Be Like Ukraine
这是激发了这个问题的示例(在fpish上) - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接