【问题标题】:F# alternate constructor assigning values to (mutable) let bindingsF# 备用构造函数为(可变的)let 绑定赋值
【发布时间】:2011-12-17 02:18:54
【问题描述】:

假设我有这个类:

type Pet (name:string) as this =
    let mutable age = 5
    let mutable animal = "dog"

我希望能够基于一些序列化数据创建一个新的Pet,我用这条记录表示:

type PetData = {
    name : string
    age : int
    animal : string
}

(TLDR:我无法弄清楚构造函数的语法,该构造函数将采用 PetData 来填充 let 绑定。​​接下来是我的各种尝试。)

所以我创建了一个新的 Pet 构造函数,它将为 let 绑定赋值。我尝试使用类初始化语法:

new (data:PetData) =
    Pet(name,
        age = data.age,
        animal = data.animal
    )

嗯,不:No accessible member or object constructor named 'Pet' takes 1 arguments. The named argument 'age' doesn't correspond to any argument or settable return property for any overload.

我检查以确保我掌握了所有语法:没有缺少逗号,正确的“赋值”(cough)运算符,正确的缩进。

好的,我试试记录初始化语法。

new (data:PetData) =
    {
        name = data.name;
        age = data.age;
        animal = data.name
    }

错误:The type 'Pet' does not contain a field 'name'

好的,所以我需要调用主构造函数。我想大概有两个地方可以放,所以我们两个都试试:

new (data:PetData) =
    {
        Pet(data.name);
        age = data.age;
        animal = data.name
    }

没有:Invalid object, sequence or record expression

new (data:PetData) =
    Pet(data.name)
    {
        age = data.age;
        animal = data.name
    }

不:This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.

我不想这样做,但也许因为字段是可变的,我可以在初始化对象后为其赋值:

new (data:PetData) =
    let p = Pet(data.name)
    p.age <- data.age
    p.animal <- data.animal
    p

Type constraint mismatch. The type Pet is not compatible with type PetData The type 'Pet' is not compatible with the type 'PetData'

哈哈,什么??

好的,让我们试试这个:

let assign(data:PetData) =
    this.age <- data.age
    this.animal <- data.animal

new (data:PetData) =
    let p = Pet(data.name)
    p.assign(data)
    p

The field, constructor or member 'assign' is not defined

对,所以它不能从外部访问 let 绑定。​​

那就试试会员吧:

new (data:PetData) =
    let p = Pet(data.name)
    p.Assign(data)
    p

member x.Assign(data:PetData) =
    this.age <- data.age
    this.animal <- data.animal

This is not a valid object construction expression. Explicit object constructors must either call an alternate constructor or initialize all fields of the object and specify a call to a super class constructor.

好的...让我们尝试不同的方式,使用显式字段:

type Pet =
    [<DefaultValue>]val mutable private age : int
    [<DefaultValue>]val mutable private animal : string
    val private name : string

    new(name:string) =
        { name = name }

    new(data:PetData) =
        {
            name = data.name;
            age = data.age;
            animal = data.animal
        }

Extraneous fields have been given values

那是我打我老猫的脸的时候。

还有其他想法吗?这些错误消息让我失望。我什至在 Google 上找不到其中的一半。

【问题讨论】:

    标签: f# constructor


    【解决方案1】:

    你可以这样做。

    type Pet =
        val mutable private age : int
        val mutable private animal : string
        val private name : string
    
        new (name:string) =
            { 
                name = name;
                age = 5; // or age = Unchecked.defaultof<_>;
                animal = "dog"; // or animal = Unchecked.defaultof<_>;
            }
    
        new (data:PetData) =
            {
                name = data.name;
                age = data.age;
                animal = data.animal;
            }
    

    F# 有自己的风格,看起来像这样。

    type Pet(name:string, age:int, animal:string) =
        let mutable age = age
        let mutable animal = animal
    
        new (name:string) =
            Pet(name, 5, "dog")
    
        new (data:PetData) =
            Pet(data.name, data.age, data.animal)
    

    编辑

    为每个评论请求添加了do 中使用的事件。

    type Pet(name:string, age:int, animal:string, start:IEvent<string>) =
        let mutable age = age
        let mutable animal = animal
    
        // all three constructors will call this code.
        do  start.Add (fun _ -> printf "Pet was started")
    
        new (name:string, start:IEvent<_>) =
            // an example of different logic per constructor
            // this is called before the `do` code.
            let e = start |> Event.map (fun x -> x + " from 'name constructor'")
            Pet(name, 5, "dog", e)
    
        new (data:PetData, start:IEvent<_>) =
            Pet(data.name, data.age, data.animal, start)
    

    【讨论】:

    • 第一个有效;原来它在抱怨[&lt;DefaultValue&gt;]。谢谢!
    • 顺便说一句,我正在考虑第二个,但据我所知,您最终会得到每个字段的副本——这对于大量对象。
    • 等等,不,这也不起作用——如果说构造函数参数之一是我想在do子句中绑定的事件,我不能这样做,因为在new() 方法中赋值后你就什么都没有了。
    • @Rei Miyasaka,你可以使用 do。它必须在其他构造函数之前。
    • 有趣,实际上没有。在 Reflector 中检查后发现,任何未在该类型的其他地方使用的构造函数参数都没有被制成字段。所以这行得通。
    【解决方案2】:

    让类型中的绑定是私有的,您对此无能为力。因此,您不能使用命名参数。通过创建属性,您可以这样做,但不能从 Pet 类型内部:

    type Pet (name:string) =
        let mutable age = 5
        let mutable animal = "dog"
    
        member x.Age with get () = age and set v = age <- v
        member x.Animal with get () = animal and set v = animal <- v
    
    type PetData = {
        name : string
        age : int
        animal : string
    }
    with
        member x.ToPet =
            new Pet (x.name, Age = x.age, Animal = x.animal)
    

    另一种选择是创建一个更通用的构造函数,如 Gradbot 建议的那样,直接接受 PetData 对象或所有三个参数。

    【讨论】:

    • PetData 需要在Pet 之前声明。
    • 在我的示例中,PetData 不依赖于 Pet。您可以通过在 PetData 实例上调用 ToPet 来创建 Pet 实例。
    • 是的,但是我的代码中还有其他一些东西依赖于 PetData,它们在 Pet 之前。
    • 您可以通过使用and 关键字而不是type 声明第二种类型来使两种类型相互依赖。
    • 在一个文件中,是的,但我的 Pet 类型很重要——理想情况下,甚至是在不同的程序集中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-13
    • 2021-04-03
    • 2018-01-13
    • 1970-01-01
    相关资源
    最近更新 更多