【问题标题】:Why does an overridden get-only property stay null when set in base class constructor?为什么在基类构造函数中设置时,覆盖的 get-only 属性保持为空?
【发布时间】:2018-11-30 10:49:24
【问题描述】:

我尝试了以下示例:

public class TestBase
{
    public virtual string ReadOnly { get; }

    public TestBase()
    {
        ReadOnly = "from base";
    }
}

class Test : TestBase
{
    public override string ReadOnly { get; }
    public Test()
    {
        // nothing here
    }
}

当我创建一个 Test 实例时,我看到 ReadOnly 保持为空。但为什么? 我真的不明白它的窍门,有人可以向我解释为什么会发生这种情况吗?至少我会想到一个错误,即不能在所属类之外设置只读属性。

【问题讨论】:

  • @SeM 这并不是真正的重复,因为这个问题是关于覆盖字符串属性的影响。
  • @MatthewWatson 你说的是第一个还是第二个?
  • @SeM 两者,因为这些答案都没有谈到覆盖属性。
  • 我认为这与构造函数中的 and 虚拟成员访问有关,不是吗?虽然如果你看一下它是如何编译的就很清楚:两个类都有自己的私有支持字段。子类中的那个被返回。 sharplab.io/…
  • 对于奖励积分,在基础构造函数中添加Console.WriteLine(ReadOnly);,在分配给属性之后。

标签: c# automatic-properties


【解决方案1】:

编译器如下处理;基本上,构造函数中的代码写入TestBase 中的original 支持字段。看来您的方案不支持,但是……我不知道语言团队是否考虑过这种情况。

顺便说一句:如果你想看看编译器对代码做了什么:sharplab.io

public class TestBase
{
    [CompilerGenerated]
    private readonly string <ReadOnly>k__BackingField; // note: not legal in "real" C#

    public virtual string ReadOnly
    {
        [CompilerGenerated]
        get
        {
            return <ReadOnly>k__BackingField; // the one in TestBase
        }
    }

    public TestBase()
    {
        <ReadOnly>k__BackingField = "from base";
    }
}
internal class Test : TestBase
{
    [CompilerGenerated]
    private readonly string <ReadOnly>k__BackingField;

    public override string ReadOnly
    {
        [CompilerGenerated]
        get
        {
            return <ReadOnly>k__BackingField; // the one in Test
        }
    }
}

【讨论】:

  • 在任何人抱怨“谁在乎,你不应该这样做,因为它没有给你任何东西”之前,然后考虑你可以为后代中的覆盖属性添加属性,这将是结合基类中属性的属性。所以它确实给了你一些东西,虽然它可能不是很多用途的功能,但它在序列化场景中可能很重要。
  • @LasseVågsætherKarlsen 我真的很想发出“贾里德信号”——有点像蝙蝠信号,但是......更怪。我应该吗?
  • 当然,我个人认为在构造函数中使用虚拟成员是错误的做法,但编译器并没有明确警告它并且确实有效,所有常见的记录警告什么可能会中断以及为什么会中断,所以在这里我确实希望分配该属性,尽管我完全理解为什么不分配。也许这种特殊情况需要明确处理,无论是警告还是实际支持。
  • 虽然我觉得为此添加全面支持听起来像一罐虫子,但是是的,我相信知识和意志的决定,所以点亮泛光灯并让团队决定:)
  • 除了可能添加警告之外,没有明智的方法可以解决此问题。只需尝试在基本构造函数中添加一个Console.WriteLine(ReadOnly);,在分配给ReadOnly 之后,您会看到不同的症状,它将调用我们知道尚未分配的属性的后代getter,因此尝试打印null。如果有的话,这里唯一合理的解决方法是编译后代类应该警告您实际上有两个不同的属性 implementations
【解决方案2】:

解释这一点的最简单方法是考虑编译器生成什么代码来实现这一点。

基类等价于:

public class TestBase
{
    public virtual string ReadOnly => _testBaseReadOnly;

    public TestBase()
    {
        _testBaseReadOnly = "from base";
    }

    readonly string _testBaseReadOnly;
}

派生类等价于:

class Test : TestBase
{
    public override string ReadOnly => _testReadOnly;

    readonly string _testReadOnly;
}

这里要注意的重要一点是,派生类有自己的 ReadOnly 支持字段 - 它不会重复使用基类中的支持字段。

意识到这一点后,为什么被覆盖的属性为空应该很明显了。

这是因为派生类有自己的 ReadOnly 支持字段,并且它的构造函数没有初始化该支持字段。

顺便说一句,如果您使用Resharper,它实际上会警告您没有在派生类中设置ReadOnly

 "Get-only auto-property 'ReadOnly' is never assigned."

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-21
    • 2011-07-06
    • 2013-03-22
    • 1970-01-01
    相关资源
    最近更新 更多