对象初始值设定项中的只读字段

本文关键字:读字段 对象 | 更新日期: 2023-09-27 18:28:43

我想知道为什么不能执行以下操作:

struct TestStruct
{
    public readonly object TestField;
}
TestStruct ts = new TestStruct {
    /* TestField = "something" // Impossible */
};

对象初始值设定项难道不能设置字段的值吗?

对象初始值设定项中的只读字段

Object Initializer在内部使用一个临时对象,然后将每个值分配给属性。有一个只读字段会打破这种局面。

之后

TestStruct ts = new TestStruct 
{
     TestField = "something";
};

将转换为

TestStruct ts;
var tmp = new TestStruct();
tmp.TestField = "something"; //this is not possible
ts = tmp;

(这是Jon Skeet的回答,解释了使用对象初始化器使用临时对象,但使用了不同的场景)

readonly表示只能在构造函数(或字段初始化器)中设置字段。对象初始值设定项中指定的属性是在构造函数返回后设置的。也就是说,

TestStruct ts = new TestStruct {
    TestField = "something"
};

基本上等同于

TestStruct ts = new TestStruct();
ts.TestField = "something";

(在Debug构建中,编译器可能会使用一个临时变量,但你已经明白了。)

C#9 Init-Only属性,尽管名称不同,也将允许初始值设定项语法设置只读字段。

以下是从链接中复制的相关部分。

仅初始化属性

下面是一个简单的对象初始化器示例。

new Person
{
    FirstName = "Scott",
    LastName = "Hunter"
}

今天的一个大限制是,属性必须是可变的,对象初始化器才能工作:它们的工作方式是首先调用对象的构造函数(在这种情况下是默认的无参数构造函数),然后分配给属性设置器。

仅初始化属性可修复此问题!他们引入了一个init访问器,它是set访问器的变体,只能在对象初始化期间调用:

public class Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

使用此声明,上面的客户端代码仍然是合法的,但任何后续对FirstNameLastName属性的赋值都是错误的。

初始化访问器和只读字段

因为init访问器只能在初始化期间调用,所以允许它们对封闭类的readonly字段进行变异,就像在构造函数中一样。

public class Person
{
    private readonly string firstName;
    private readonly string lastName;
    
    public string FirstName 
    { 
        get => firstName; 
        init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
    }
    public string LastName 
    { 
        get => lastName; 
        init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
    }
}

这是不可能的。因为CCD_ 8字段不能从CCD_ 9或CCD_。

你展示的实际上是object initializer。这只是一个语法糖,被搞成这样的

TestStruct ts;
TestStruct ts1 = new TestStruct();
ts1.TestField = value;
ts = ts1;

这清楚为什么它不编译吗?

我想知道为什么不可能做到以下几点:

因为编译器无法确定以下代码将被执行:

TestStruct ts = new TestStruct 
{
    TestField = "something"
};

您应该直接内联或在构造函数内部初始化只读成员。

来自MSDN

readonly关键字是一个可以在字段中使用的修饰符。什么时候字段声明包括一个只读修饰符声明引入的字段只能作为声明或在同一类的构造函数中

因此,这是不可能的,因为对象初始化器只是创建后的赋值。

因为对象初始化器只是初始化的一种简单方法

TestStruct ts = new TestStruct {
  TestField = "something";
};

与相同(编译器将把上面的内容翻译成这个):

TestStruct ts = new TestStruct();
ts.TestField = "something";//this is of course not allowed.

在只读字段扩展CollectionBase的情况下,我遇到了一个有趣的"异常"。

这是代码:

using System.Collections;
namespace ReadOnly
{
    class Program
    {
        static void Main(string[] args)
        {
            Foo foo1 = new Foo()
            {
                Bar = new Bar()  // Compile error here - readonly property.
                {
                    new Buzz() { Name = "First Buzz" }
                }
            };
            Foo foo2 = new Foo()
            {
                Bar = // No Compile error here.
                {
                    new Buzz { Name = "Second Buzz" }
                }
            };
        }
    }
    class Foo
    {
        public Bar Bar { get; }
    }
    class Bar : CollectionBase
    {
        public int Add(Buzz value)
        {
            return List.Add(value);
        }
        public Buzz this[int index]
        {
            get { return (Buzz)List[index]; }
            set { List[index] = value; }
        }
    }
    class Buzz
    {
        public string Name { get; set; }
    }
}

Foo1是我最初尝试的方法(所有这些类都来自一个外部库,所以我们一开始并不知道Bar是只读的)。得到编译错误。然后我不小心像foo2一样重新输入了它,它起作用了。

在反编译dll并看到Bar扩展了CollectionBase之后,我们意识到第二个语法(foo2)正在调用集合上的Add方法。因此,在集合的情况下,虽然不能设置只读属性,但可以通过对象初始化器调用Add方法。