Home > Back-end >  Covariant return type on auto property remains null when initalized in constructor
Covariant return type on auto property remains null when initalized in constructor

Time:10-09

I got a problem with auto property initialization with covariant return type. In the BaseClass constructor, there is no exception but the auto property remains null.

I found a workaround, by overriding the property and make a cast in the body of the get (see in comment below).

Do you have got an explanation of the behavior ? And is there a better workaround ?

Regards,

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Hello World!");
        Console.WriteLine(new FinalClass(new FinalProperty()).MyProperty == null); // true
    }
}

public class BaseClass
{
    public BaseClass(BaseProperty prop)
    {
        MyProperty = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }

    public virtual BaseProperty MyProperty { get; }
}



public class FinalClass : BaseClass
{
    public FinalClass(BaseProperty prop)
        : base(prop)
    { }

    public override BaseProperty MyProperty { get; }

    //public override FinalProperty MyProperty
    //{
    //    get { return (FinalProperty)base.MyProperty; }
    //}
}

public class BaseProperty
{ }

public sealed class FinalProperty : BaseProperty
{ }

CodePudding user response:

Why this is happening - for every MyProperty overriden in child clas using auto property compiler will generate the new backing field for that class i.e. next:

public class FinalClass : BaseClass
{
    public FinalClass(FinalProperty prop)
            : base(prop)
    { }

    public override FinalProperty MyProperty { get; }
}

Will be transformed by compiler into something like:

public class FinalClass : BaseClass
{
    [CompilerGenerated]
    private readonly FinalProperty <MyProperty>k__BackingField;

    public virtual FinalProperty MyProperty
    {
        [PreserveBaseOverrides]
        [CompilerGenerated]
        get
        {
            return <MyProperty>k__BackingField;
        }
    }

    public FinalClass(FinalProperty prop)
        : base(prop)
    {
    }
}

So BaseClass(BaseProperty prop) invoked by base(prop) will set backing field generated for MyProperty in BaseClass while FinalClass.MyProperty will use the one generated for it.

Another possible workaround is to use generics instead of covariant returns:

public class BaseClass<T> where T: BaseProperty
{
    public BaseClass(T prop)
    {
        MyProperty = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }
    public T MyProperty { get; }
}

public class FinalClass : BaseClass<FinalProperty>
{
    public FinalClass(FinalProperty prop)
        : base(prop)
    { }
}

CodePudding user response:

Although you have overridden MyProperty in FinalClass by doing this:

public override BaseProperty MyProperty { get; }

The fact that this is an auto-property still creates an extra backing field in FinalClass. Let's remove the syntactic sugar of the auto-properties to see what's going on:

public class BaseClass
{
    public BaseClass(BaseProperty prop)
    {
        // notice that when setting a get-only field in the constructor, 
        // you are actually setting its backing field
        backingFieldMyPropertyInBaseClass = prop;
        Console.WriteLine($"ctor A : {MyProperty == null}"); // true
    }

    private BaseProperty backingFieldMyPropertyInBaseClass;
    public virtual BaseProperty MyProperty { 
        get { return backingFieldMyPropertyInBaseClass; }
    }
}
public class FinalClass : BaseClass
{
    public FinalClass(BaseProperty prop)
        : base(prop)
    { }

    private BaseProperty backingFieldMyPropertyInFinalClass;
    public override BaseProperty MyProperty { 
        get { return backingFieldMyPropertyInFinalClass; }
    }
}

Notice how backingFieldMyPropertyInFinalClass is never set.

Making MyProperty in FinalClass return base.MyProperty fixes this because base.MyProperty actually returns the field you want - backingFieldMyPropertyInBaseClass. Other ways of making it print false in Main include,

  • setting MyProperty in the constructor of FinalClass, so that backingFieldMyPropertyInFinalClass is set.
  • adding a protected setter to MyProperty, so that when setting it in the constructor, it calls the overridden setter, rather than setting the backing field. This will also make it print false in the constructor of BaseClass.
  • Related