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

时间:2013-08-22 15:48:33

标签: c# readonly

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

struct TestStruct
{
    public readonly object TestField;
}

TestStruct ts = new TestStruct {
    /* TestField = "something" // Impossible */
};

对象初始值设定项不应该能够设置字段的值 ?

8 个答案:

答案 0 :(得分:10)

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

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

基本等同于

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

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

答案 1 :(得分:9)

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

关注

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

会转化为

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

(这是带有对象初始化器的answer from Jon Skeet explaining the usage of temporary object,但有不同的场景)

答案 2 :(得分:3)

C# 9 Init-Only Properties(尽管有名称)将允许初始化器语法能够set readonly fields as well

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

Init-only properties

这是对象初始化程序的简单示例。

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

当今的一个最大限制是,该属性必须是 mutable 才能使对象初始化程序起作用:它们通过首先调用对象的构造函数(在这种情况下为默认的,无参数的)来起作用,然后分配到属性设置器。

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

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

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

Init accessors and readonly fields

由于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)));
    }
}

答案 3 :(得分:2)

这是不可能的。由于readonly字段无法从ConstructorField Initializer以外的地方分配。

您展示的内容实际上是object initializer。它只是一种合成糖,被归结为类似的东西

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

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

答案 4 :(得分:1)

  

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

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

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

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

答案 5 :(得分:1)

来自MSDN

  

readonly关键字是您可以在字段上使用的修饰符。什么时候   字段声明包括一个只读修饰符,赋值给   声明引入的字段只能作为其中的一部分出现   声明或在同一类的构造函数中

因为对象初始化器只是创建后分配,所以它还不可能。

答案 6 :(得分:1)

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

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

与(编译器将上述内容翻译成)相同:

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

答案 7 :(得分:-1)

在readonly字段扩展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方法。