F#辅助构造函数调用带属性初始化构造函数的不合法原因
首先给出基础类定义:
type A() = member val Prop: int = 0 with get, set
这个类默认有无参构造函数,实例化时可以通过三种方式设置属性值:
let a0 = A() // 实例化后Prop取默认值0 let a1 = A(Prop = 1) // 用对象初始化器在实例化阶段给Prop赋值 let a2 = A() a2.Prop <- 2 // 实例化完成后单独给属性赋值
如果要给类加一个传入Prop值的构造函数,同时保留原无参构造,有人尝试写了如下实现:
type A1() = member val Prop: int = 0 with get, set new(prop: int) = A1(Prop = prop) // 此处触发编译错误
这段代码会抛出如下编译错误:
这不是有效的对象构造表达式。显式对象构造函数必须调用备用构造函数,或初始化对象所有字段并指定超类构造函数调用。
从逻辑上看这段代码似乎没什么问题:新构造函数调用了原无参构造,同时给Prop赋了值,和静态工厂方法的逻辑几乎一致,但始终无法通过编译。目前常见的可行替代方案有两种:
// 方案1:静态工厂方法 type A2() = member val Prop: int = 0 with get, set static member Create(prop: int) = A2(Prop = prop) let a21 = A2.Create(1) // 方案2:主构造函数加可选参数 type A3(?prop: int) as this = do if prop.IsSome then this.Prop <- prop.Value member val Prop: int = 0 with get, set let a31 = A3(1)
F#对类内部用new关键字定义的显式附加构造函数,有非常明确的语法限制:new(...) = 后面只能跟纯粹的构造函数链式调用,也就是只能调用本类其他构造函数或者父类构造函数,传入的参数必须严格匹配目标构造函数的形参列表,不能在这个调用里夹带对象初始化器的属性赋值语法。
你写的A1(Prop = prop)本质上不是“调用一个接收Prop参数的A1构造函数”,而是“先调用A1无参构造函数,再通过对象初始化器给Prop属性赋值”——这个表达式属于对象初始化表达式,不是纯粹的构造函数调用,自然不满足附加构造函数的语法校验规则。
而静态工厂方法里写A2(Prop = prop)能正常运行,是因为类的普通成员/静态成员里的代码没有这个构造链的特殊校验限制,编译器会直接把它识别为正常的“实例化对象+初始化属性”操作,不会报错。
如果一定要用附加构造函数的方式实现需求,不需要绕到静态方法或者可选参数,只要把属性赋值从初始化器挪到构造链调用后的then块里就行,合法写法如下:
type A4() = member val Prop: int = 0 with get, set new(prop: int) as this = A4() then this.Prop <- prop
这段代码的运行逻辑和你最初写的A1完全一致,只是符合了F#附加构造函数的语法要求,可以正常编译使用。
内容的提问来源于stack exchange,提问作者Franco Tiveron

