不太清楚这是如何工作的。甚至不确定在构建类时如何使用对象初始化程序Child。然后它如何覆盖我的默认Test类。

internal class Program
{
    static void Main(string[] args)
    {
        Child child = new Child
        {
            Test = { Name = "hello" }
        };

        //child.Test.Name prints out "hello"
    }
}

public class Child : Parent
{
    public Child()
    {
    }
}

public abstract class Parent
{
    public Test Test { get; }

    protected Parent()
    {
        Test = new Test { Name = "Default" };
    }
}

public class Test
{
    public string Name { get; set; }
}

预期出现错误,我不应该能够使用对象初始值设定项Test,并且默认值无法更改。

0


5 个回答
5

您正在考虑

Test = new Test { Name = "hello" }

这确实是不允许的,因为Test没有 setter。

另一方面,

Test = { Name = "hello" }

完全没问题,因为它没有设置Test。它只是设置Test.Name,并且Name确实有一个 setter。

来看,

在等号后指定对象初始化器的成员初始化器是嵌套对象初始化器,即嵌入对象的初始化。嵌套对象初始化器中的赋值不会为字段或属性赋新值,而是被视为对字段或属性成员的赋值。

这类似于:

Child child = new Child();
child.Test.Name = "hello";

这没有什么违法的。

Test可以说,当它(对 的访问)是通过 进行赋值时set,与通过 进行变异时,有点不清楚get,但这里的一个重要线索是缺少new;只是使用= {...}通过 进行get

正如其他人指出的那样 – 如果您使用new Test { ... },您实际上会创建一个新实例,这在这里是不允许的,因为您无法直接将新值分配给 Test 属性,因为它没有set访问器;而{ ... }这只是您如何在已经创建和实例化的现有实例中为现有属性指定新值。您没有创建新实例 – 这就是new关键字的用途。

进行仔细检查

这:

Child child = new Child
{
    Test = { Name = "hello" }
};

在较低级别的 C#(IL 代码)中变为以下内容:

Child child = new Child();
child.Test.Name = "hello";
Child child2 = child;

如您所见,您已经Test通过类的构造函数初始化了属性Parent。只有这样,您才只需为Name现有实例的属性分配一个新值 – 您不会更改 Test 属性本身。

如果派生类有构造函数,而基类也有构造函数,则派生类将调用基类的构造函数。它与以下代码完全相同:

public class Foo
{
    public bool Bar { get; set; }

    protected Foo()
    {
        Bar = true;
    }
}

public sealed class Baz : Foo
{
    public Baz() : base() // <-- Notice 'base()' - it's completely optional
    {
    }
}

您还可以注意到基类的构造函数是如何首先被调用的:

public class Foo
{
    protected Foo()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Message {i} from Foo constructor");
        }
    }
}

public sealed class Bar : Foo
{
    public Bar() // : base() is completely optional here
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine($"Message {i} from Bar constructor");
        }
    }
}

// ...
var bar = new Bar();

结果:

Message 0 from Foo constructor
Message 1 from Foo constructor
Message 2 from Foo constructor
Message 3 from Foo constructor
Message 4 from Foo constructor
Message 0 from Bar constructor
Message 1 from Bar constructor
Message 2 from Bar constructor
Message 3 from Bar constructor
Message 4 from Bar constructor

因此Parent该类初始化该Test属性。

0

protected Parent()
{
    Test = new Test { Name = "Default" };
}

这会Test在创建时为属性分配其唯一值Parent。此后,Test就一成不变了 —您无法Test为其分配新对象

但是由于Test它本身具有Name仍然是常规 get; set; 的属性,因此您可以将其更改Name为任何您想要的。如果您还想Name不可触碰,则必须删除 中的 setter Test

令人惊讶的是这个语法:

Child child = new Child
{
    Test = { Name = "hello" }
}

它不会尝试分配一个新Test对象(由于它是只读的,因此会失败)。相反,它会修改Test在构造函数中创建的现有对象Parent

顺序如下:

  1. new Child()运行
  2. Parent()Test构造函数使用“默认”创建
  3. 对象初始化器修改现有的Test.Name

为了防止这种情况,请将其设为Name只读:

public class Test
{
    public string Name { get; private set; }
}

它与集合初始值设定项的工作方式类似:

public List<int> Numbers { get; } = new();
var obj = new MyClass 
{
    Numbers = { 1, 2, 3 }  // Adds to existing list
};

希望这有帮助!👍