Question

Why does an overridden get-only property stay null when set in base class constructor?

I tried the following example:

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
    }
}

When I create an instance of Test, I see that ReadOnly stays null. But why? I really do not get the hang of it, could somebody please explain to me why this happens? At least I would expect an error, that a read-only property cannot be set outside of the owning class.

 46  2901  46
1 Jan 1970

Solution

 39

The compiler treats this as below; basically, the code in the constructor writes to the original backing field, in TestBase. It seems that yours is not a supported scenario, but... I do wonder whether the language team have considered this case.

BTW: if you ever want to see what the compiler does with code: 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
        }
    }
}
2018-11-30

Solution

 17

The easiest way to explain this is to consider what code the compiler is generating to implement this.

The base class is equivalent to this:

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

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

    readonly string _testBaseReadOnly;
}

The derived class is equivalent to this:

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

    readonly string _testReadOnly;
}

The important thing to note here is that the derived class has its OWN BACKING FIELD for ReadOnly - it does NOT re-use the one from the base class.

Having realised that, it should be apparent why the overridden property is null.

It's because the derived class has its own backing field for ReadOnly, and its constructor is not initialising that backing field.

Incidentally, if you're using Resharper it will actually warn you that you're not setting ReadOnly in the derived class:

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