【问题标题】:multiple constructors and class inheritance in F#F# 中的多个构造函数和类继承
【发布时间】:2014-12-03 21:31:46
【问题描述】:

我很难将以下 C# 代码转换为 F#:

class Foo
{
    public Foo() { }
    public Foo(string name) { }
}

class Bar : Foo
{
    public Bar() : base() { }
    public Bar(string name) : base(name) { }
    public string Name { get; set; }
}

我第一次尝试关注,但是报错

“Bar”类型的构造函数必须直接或间接调用它的 隐式对象构造函数。使用对隐式对象的调用 构造函数而不是记录表达式。

type Foo() =
    new(name:string) = Foo()

type Bar() =
    inherit Foo()
    new(name:string) = { inherit Foo(name) }
    member val Name:string = null with get, set

然后我尝试了以下,但它现在在自动属性上报告错误

'member val' 定义只允许在具有主要的类型中 构造函数。考虑为您的类型定义添加参数”

type Foo() =
    new(name:string) = Foo()

type Bar =
    inherit Foo
    new(name:string) = { inherit Foo(name) }
    member val Name:string = null with get, set

【问题讨论】:

    标签: f#


    【解决方案1】:

    如果您希望 F# 源代码编译为与您的 C# 代码提供的 API 完全相同的 API,答案如下:

    type Foo =
        new() = {}
        new(name:string) = { }
    
    type Bar =
        inherit Foo
    
        [<DefaultValue>] 
        val mutable private name:string
    
        new() = { inherit Foo() }
        new(name) = { inherit Foo(name) }
    
        member x.Name with get() = x.name and set v = x.name <- v
    

    【讨论】:

    • 在这种情况下,您仍然无法添加以下内容:member val Name = null with get,set
    • 是的,我监督了这一点。我已经编辑了 Bar 类以包含一个 Name 属性。它不是自动属性,但 API 和语义与 C# 的情况完全相同。没有其他方法可以“按字母”复制您的 C# 示例。
    • 同意;这是目前最接近的解决方法。
    • 我收到以下编译器错误:No constructors are available for the type 'Foo'。修复是new() = { inherit Foo() }
    • @DharmaTurtle:很高兴它现在对你有用;仅供参考,您提到的那行已经存在于答案中。
    【解决方案2】:

    如果一个类在它的名字后面有一个参数列表(包括()),它有一个主构造函数。使用它,任何inherit 声明都只放置在这个主构造函数中,它直接位于类声明之后和任何member 声明之前。

    目前尚不清楚您要达到的目标。 Foo 类有一个构造函数,它接受一个字符串参数,只是为了丢弃它。一对(技术上)有效的相似类是这样的:

    type Foo(name:string) =
        member f.NameLength = name.Length
    
    type Bar(initialName) = // WARNING: this will not end well
        inherit Foo(initialName)
        member val Name:string = initialName with get, set
    

    但这不是明智的代码。 即使Bar 中的名称被更改,Foo 也会保留初始名称。 Bar.Name.Length 返回当前名称的长度,而Bar.NameLength 返回初始名称的长度。

    要保留默认构造函数,可以添加new () = Bar(null)(或Foo 中的等效项),但请注意null 被视为仅互操作功能。它不在面向 F# 的代码中使用;如果可能,分别使用适当的选项类型或空字符串(取决于字符串是空的还是根本不存在)。

    此外,F# 组件设计指南不鼓励继承类——这是有充分理由的。用例很少,但通常涉及一个很小的基类和一个完美超集的派生类。通过使用一个类作为另一个类的成员来组合类型更为常见。


    我不知道这有多相关,但这里有一个具有默认构造函数的类和一个使用它的附加构造函数的示例:

    type Text500(text : string) =
        do if text.Length > 500 then
            invalidArg "text" "Text of this type cannot have a length above 500."
        member t.Text = text
        new () = Text500("")
    

    这利用主构造函数来验证输入,并有一个额外的、使用空字符串的无参数构造函数。 (我不确定额外的构造函数在实际应用中是否有用。)

    【讨论】:

    • 我正在尝试使用 WPF,我怀疑这里的空构造函数会对此有所帮助。
    【解决方案3】:

    这样编译:

    type Foo() =
        new(name:string) = Foo()
    
    type Bar(name : string) =
        inherit Foo()
        new() = Bar(null) // or whatever you want as a default.
        member val Name:string = name with get, set
    

    请参阅 Constructors (F#)Inheritance (F#)

    查看反编译,C# 将是(删除属性):

    public class Bar : Program.Foo {
        internal string Name@;
    
        public string Name {
            get {
                return this.Name@;
            }
            set {
                this.Name@ = value;
            }
        }
    
        public Bar(string name) {
            this.Name@ = name;
        }
    
        public Bar() : this(null) {
        }
    }
    
    public class Foo {
        public Foo() {
        }
    
        public Foo(string name) : this() {
        }
    }
    

    【讨论】:

    • 谢谢;但这不再是相同的 C# 代码,对吧?我知道示例 C# 代码不是真正的代码,但关键是看看我们是否可以在 F# 中实现精确的逻辑......如果没有其他好的替代方案可以解决这个问题,我会在几天内接受你的答案。
    • 确实不太一样(Bar.Name 被赋予了来自 string 构造函数的值),但是 - IMO - 更有意义(如果Name 属性更有意义是在基本类型中)。当然,有些区别是反汇编的 IL 显示了该字段的实现细节。
    猜你喜欢
    • 2011-07-06
    • 1970-01-01
    • 1970-01-01
    • 2012-09-03
    • 2015-06-20
    • 1970-01-01
    • 2014-11-22
    相关资源
    最近更新 更多