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 ofFinalClass
, so thatbackingFieldMyPropertyInFinalClass
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 ofBaseClass
.