C# 9中Init-Only和ReadOnly有什么区别?

24
我正在学习即将发布的C# 9新特性。其中引入了Init-Only属性。
目前的一个主要限制是,对象初始化器只能用于可变属性:它们首先调用对象的构造函数(在此情况下为默认的无参数构造函数),然后再将其分配给属性设置器。
Init-only属性解决了这个问题!它们引入了一个init访问器,它是set访问器的一种变体,只能在对象初始化期间调用:
public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

通过这个声明,上面的客户端代码仍然是合法的,但是对FirstName和LastName属性的任何后续赋值都是错误的。这行代码是什么意思?如果ReadOnly也有同样的作用,那么Init-Only属性有什么用处?

3
没问题,这确实是一个很好的实用性问题。但有些人喜欢捉弄别人。 - Vivek Nuna
4个回答

21
根据新的C# 9 特性文章,所述:

今天的一个重大限制是属性必须是可变的, 才能让对象初始化程序起作用:它们通过首先调用对象的构造函数(在这种情况下是默认的、无参数的构造函数), 然后分配给属性setter来完成。

但是,带有readonly修饰符的值类型是不可变的,如readonly文档所述。

因此,不可能使用只读属性与对象初始化器。

但是,使用初始化器唯一的属性,您可以使用对象初始化程序。


谢谢,除了这个区别之外,它们在所有方面都是相同的吗?换句话说,我可以说使用对象初始化器来读取只读属性,它们已经提供了 Init_Only 属性吗? - Vivek Nuna
我认为这是实现这个新功能的主要原因,但是没有更多关于实现细节的信息,我不确定。 - emre nevayeshirazi
你能提供一个例子来展示这两者之间的区别吗? - Vivek Nuna
1
@viveknuna 去看看 https://mybuild.microsoft.com/sessions/fc099bf7-8c85-4c3b-9a90-2d917342f945?source=schedule 。 - mjwills
使用init,调用者可以使用属性初始化器语法来设置值,同时仍然保持不可变性(只读属性)。 - M.Hassan
仅就C#编译器而言,“带有readonly修饰符的类型是不可变的”。但是在IL/CLR级别上,只读字段是可变的。这实际上也使它们可以通过C#源代码进行修改,例如通过反射来改变只读字段的值... - user19858830

14
Init-only属性的目的是只允许在对象初始化器或类的构造函数中进行赋值。
如果我们考虑你的Person类,可以在构造函数或对象初始化器中对FirstName或LastName进行赋值,但是在其他地方不能对FirstName或LastName进行赋值。
public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }

    // this is a constructor
    public Person()
    {
        this.FirstName = "first name"; // works
        this.LastName = "last name"; // works
    }
}

//this is a random method
public SomeMethod()
{
    Person myPerson = new Person
    {
        FirstName = "first name", // works
        LastName = "last name" // works
    };

    myPerson.FirstName = "change the first name"; // Error (CS8852)
    myPerson.LastName = "change the last name"; // Error (CS8852)
}

不过这不是一个好的例子:如果 Person 的构造函数从未将 LastName 赋值,如果您执行 Person p = new Person() { FirstName = "bob" }(并且 从不 分配到 LastName),那么该程序无意中违反了 class Person 的契约,因为 LastName 现在是 null,尽管被类型化为 String(而不是 String?),这会破坏 C# 的可空引用类型检查功能。 - Dai
@Dai,我不明白你为什么说这不是一个好的例子,“如果Person的构造函数从未分配给LastName...” 首先,在这种情况下并非如此。其次,还有第二个条件:启用可空性。如果尽管所有这些条件,某人发现自己处于您描述的情况中,CS8616会让他们知道存在问题。 - nalka
好的,这是另一个条件(而且在我的示例中也没有满足)。确实,CS8616不会弹出,但你会得到CS0843。如果你禁用了可空性检查能力,那么就没有CS8616的存在意义了。 - nalka
是的,你将学习关于结构体的CS0843和其他内容的CS8616。 - nalka
不,那是不正确的:CS0843 是指当一个 class 有未赋值的非空引用类型成员时,而 CS0843 是指当一个 struct 有未赋值的成员(无论是引用类型还是值类型),不是 当一个 class 有未赋值的 struct 或其他值类型成员时(这时你根本不会收到任何警告,这是很危险的,因为 default(TValue) 对于值类型是有意义的,例如 enum 值)。 - Dai
显示剩余2条评论

2
这也可以防止构造函数变得臃肿,当你有很多可选参数时。例如,在下面的类中:
    public class InitOnlyExample
{
    private readonly string _firstName;
    private readonly string _lastName;
    private readonly string _address;
    private readonly int _zipCode;
    private readonly string _phoneNumber;

    public InitOnlyExample(
        string firstName,
        string lastName,
        string address,
        int zipCode = default,
        string phoneNumber = default
    )
    {
        _firstName = firstName;
        _lastName = lastName;
        _address = address;
        _zipCode = zipCode;
        _phoneNumber = phoneNumber;
    }

    public string FirstName { get { return _firstName; } }

    public string LastName { get { return _lastName; } }

    public string Address { get { return _address; } }

    public int ZipCode { get { return _zipCode; } }

    public string PhoneNumber { get { return _phoneNumber; } }

}

-2

与设置初始值的属性不同,只读属性只能在构造函数中或使用属性初始化器中设置。


2
你的回答并没有太多价值。你有什么新的补充吗,其他回答没有涉及到的? - Jeremy Lakeman

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