Почему переопределенное свойство get-only остается равным null при установке в конструкторе базового класса?

Я попробовал следующий пример:

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 остается нулевым. Но почему? Я действительно не понимаю, может ли кто-нибудь объяснить мне, почему это происходит? По крайней мере, я ожидал бы и ошибки, что свойство только для чтения не может быть установлено за пределами класса-владельца.

Ответ 1

Компилятор рассматривает это, как показано ниже; в основном, код в конструкторе записывается в исходное поле поддержки в TestBase. Кажется, что ваш сценарий не поддерживается, но... Я задаюсь вопросом, рассмотрела ли языковая команда этот случай.

BTW: если вы когда-нибудь захотите узнать, что делает компилятор с кодом: 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
        }
    }
}

Ответ 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 - он НЕ повторно использует тот из базового класса.

Поняв это, должно быть понятно, почему переопределенное свойство имеет значение null.

Это потому, что производный класс имеет собственное поле поддержки для ReadOnly, и его конструктор не инициализирует это поле поддержки.

Кстати, если вы используете Resharper он действительно предупредит вас, что вы не устанавливаете ReadOnly в производном классе:

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

Ответ 3

Поскольку базовый класс определяет поведение, которое должен придерживаться любой класс (см. Принцип подстановки Лискова), но не ограничивает (и не должен) ограничивать его поведение.

Базовый класс может определять только поведение, и он не может (и не должен) указывать, что поведение "не должно быть" (т.е. "Не должно быть доступно для записи на конкретных уровнях").

"Только для чтения" - это только то значение, которое вы ему назначаете. В С# нет каких-либо релевантных гарантий. У вас может быть свойство get-only, которое изменяется каждый раз, или свойство get/set, в котором установщик выдает InvalidOperationException ( "Этот экземпляр доступен только для чтения").