Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can a full property in C# be overridden with only a getter but it can still be set?

I came across a behavior that surprises me. Given the following two classes:

class Parent
{
    public virtual bool Property { get; set; }
}

class Child : Parent
{
    public override bool Property { get => base.Property; }
}

I can write code like this:

Child child = new Child();
child.Property = true; // this is allowed

The IDE makes it confusing, too, because, while it allows the assignment, it also indicates that the overridden property is read-only:

enter image description here

Furthermore, this override is only allowed when I'm using the base class' getter:

enter image description here

What is going on here?

like image 479
rory.ap Avatar asked May 16 '18 17:05

rory.ap


1 Answers

I'll take a crack at this.

It looks like this may just be a bug with Intellisense, where it is unable to find the base implementation of an auto-property. The code is valid and makes sense - here's another way to express your example.

Child child = new Child();
child.SetProperty(true);

class Parent
{
    private bool _property;

    public virtual bool GetProperty() => _property;
    public virtual void SetProperty(bool value) => _property = value;
}

class Child : Parent
{
    public override bool GetProperty() => base.GetProperty();
}

With this representation, it's now obvious why overriding GetProperty is fine. Here's the relevant IL for your code:

Main:
IL_0000:  newobj      Child..ctor
IL_0005:  ldc.i4.1
IL_0006:  callvirt    Parent.set_Property
IL_000B:  ret

Parent.get_Property:
IL_0000:  ldarg.0
IL_0001:  ldfld       Parent.<Property>k__BackingField
IL_0006:  ret

Parent.set_Property:
IL_0000:  ldarg.0
IL_0001:  ldarg.1
IL_0002:  stfld       Parent.<Property>k__BackingField
IL_0007:  ret

Parent..ctor:
IL_0000:  ldarg.0
IL_0001:  call        System.Object..ctor
IL_0006:  ret

Child.get_Property:
IL_0000:  ldarg.0
IL_0001:  call        Parent.get_Property
IL_0006:  ret

Child..ctor:
IL_0000:  ldarg.0
IL_0001:  call        Parent..ctor
IL_0006:  ret

And here's my version:

Main:
IL_0000:  newobj      Child..ctor
IL_0005:  ldc.i4.1
IL_0006:  callvirt    Parent.SetProperty
IL_000B:  ret

Parent.GetProperty:
IL_0000:  ldarg.0
IL_0001:  ldfld       Parent._property
IL_0006:  ret

Parent.SetProperty:
IL_0000:  ldarg.0
IL_0001:  ldarg.1
IL_0002:  stfld       Parent._property
IL_0007:  ret

Parent..ctor:
IL_0000:  ldarg.0
IL_0001:  call        System.Object..ctor
IL_0006:  ret

Child.GetProperty:
IL_0000:  ldarg.0
IL_0001:  call        Parent.GetProperty
IL_0006:  ret

Child..ctor:
IL_0000:  ldarg.0
IL_0001:  call        Parent..ctor
IL_0006:  ret     

Note that this is different than public override bool Property { get; }, being shorthand for instructing the compiler to generate a single getter override for a backing property of the same name, with no mention of the preexisting setter. Somebody experienced with the actual spec will definitely be able to offer more information around this, however.

like image 136
Scott Avatar answered Nov 15 '22 20:11

Scott