在F#中不允许写入结构记录的可变属性。为什么?

时间:2017-07-03 03:49:07

标签: f#

当我有以下代码时:

[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}

let john = { FirstName = "John"; LastName = "Connor"}

john.FirstName <- "Sarah";

编译器抱怨&#34;一个值必须是可变的才能改变内容&#34;。但是,当我删除Struct属性时,它工作正常。为什么会这样?

1 个答案:

答案 0 :(得分:13)

这可以保护您免受几年前曾困扰C#世界的陷阱:结构按值传递

请注意,红色波浪形(如果您在IDE中)不在FirstName下,而是在john下。编译器抱怨不是要更改john.FirstName的值,而是更改john本身的值。

对于非结构,引用和引用对象之间存在重要区别:

enter image description here

引用和对象本身都是可变的。这样你就可以改变引用(即使它指向不同的对象),或者你可以改变对象(即改变其字段的内容)。

但是,对于结构体,这种区别不存在,因为没有引用:

enter image description here

这意味着当你改变john.FirstName时,你也会改变john本身。他们是同一个。

因此,为了执行此突变,您需要将john本身声明为可变:

[<Struct>]
type Person = { mutable FirstName:string ; LastName : string}

let mutable john = { FirstName = "John"; LastName = "Connor"}

john.FirstName <- "Sarah"  // <-- works fine now

如需进一步说明,请在C#中尝试:

struct Person
{
    public string FirstName;
    public string LastName;
}

class SomeClass
{
    public Person Person { get; } = new Person { FirstName = "John", LastName = "Smith" };
}

class Program
{
    static void Main( string[] args )
    {
        var c = new SomeClass();
        c.Person.FirstName = "Jack";
    }
}

IDE将有用地强调c.Person并告诉您&#34;无法修改&#39; SomeClass.Person&#39;的返回值。因为它不是变量&#34;

为什么?每次编写c.Person时,都会将其转换为调用属性getter,这就像另一种返回Person的方法一样。但由于Person是按值传递的,因此返回Person每次都会变为Person。 getter无法返回对同一对象的引用,因为不能引用struct。因此,您对此返回值所做的任何更改都不会反映在Person内的原始SomeClass中。

在此有用的编译器错误存在之前,很多人会这样做:

c.Person.FirstName = "Jack"; // Why the F doesn't it change? Must be compiler bug!

我清楚地记得几乎每天回答这个问题。那些日子! : - )