Polymorphic get-only auto-property write access

Topics: C# Language Design
Sep 1, 2014 at 12:00 AM
Hi, Roslyn team!

Continuing this story, if I have class like this:
class B {
    public virtual int Value { get; set; }
}
And derive another class, overriding this property:
class C1 : B {
    public override int Value { get; } = 42;
}
Currently, property initializer always directly assigns value to auto-property backing field, so I'm expecting this initialization bypass polymorphic setter of overridden property, ok.

As stated in "Upcoming Features in C#" document, we might expect this code also compiles and work soon in Roslyn CTPs (get-only auto-property initialized in constructor):
class C2 : B {
    public override int Value { get; }

    public C2() {
        Value = 42; // #1
    }

    public void M() {
        Value = 42; // #2
    }
}
The question is - what is the semantic of #1 and #2 property writes?
  • Is write #1 from constructor is auto-property backing field assignment (non-polymorphic);
  • Or maybe both property writes are polymorphic setter calls.
Can you please share any design details on this curious feature intersection? :)
Developer
Sep 1, 2014 at 3:03 AM
Edited Sep 1, 2014 at 3:07 AM
#1 is assignment to the backing field. I don't believe it is the intent of this feature to allow #2.
Sep 1, 2014 at 3:44 AM
If an autoproperty is public and virtual, writing to the property should be legal within any context where one has a valid reference to the underlying object, and should call the setter. Allowing a field initializer to write the backing field directly would be reasonable, but even within a constructor I see no basis for bypassing any override of the property setter.
Sep 1, 2014 at 2:27 PM
nmgafter wrote:
#1 is assignment to the backing field. I don't believe it is the intent of this feature to allow #2.
So, just by moving from constructor to method, property write will became polymorphic or not? Doesn't sounds "right" for me...
Sep 1, 2014 at 3:53 PM
ControlFlow wrote:
nmgafter wrote:
#1 is assignment to the backing field. I don't believe it is the intent of this feature to allow #2.
So, just by moving from constructor to method, property write will became polymorphic or not? Doesn't sounds "right" for me...
If an auto-property is non-virtual, I would consider it appropriate for a compiler to replace property accesses with field accesses within the class where the property is defined, since the only way the semantics of the class could be changed would be to recompile the class, whereupon the compiler could change to using property accesses if required. I would not consider such substitution proper with any virtual properties except when using property-initialization syntax.

If a get-only auto-property is virtual, I would require that its value be set with a field initializer and nowhere else [the backing field shouldn't be accessible anywhere else, and a field which could never be written would be useless].
Coordinator
Sep 1, 2014 at 7:13 PM
"Do not try to call the setter. That's impossible. Instead... only try to realize the truth. There is no setter"

Hey all,

When examining this feature it's helpful to always remember these things (believe me, we went back and forth on them in the LDMs before we finally conquered our intuitions):

1) There is no setter. There isn't one. Doesn't exist in any form. Not in source, not in metadata. No setter to set. No setter to call. No setter to override. No setter.
2) The backing field is private but its name is unutterable - you can never directly reference it in source and it has a mangled name in metadata.
3) You can only ever assign to the property in a constructor of the same class that declared it - exactly like readonly fields.
4) The assignment in the constructor writes directly to the backing field because there's no other way assignment could work (because there is no setter).
5) There's still no setter, ever.

Once your embrace those inviolable truths most questions just fall out. No assignment is ever polymorphic because there's no setter. Derived classes can never assign because there's no setter and the backing field is private. The virtualness of the property is only relevant when reading the property - never writing to it because there is no setter.

For all other situations, if a certain train of reason requires, even briefly, the existence of a setter - discard it and remember "there is no setter" :)

Regards,

Anthony D. Green, Program Manager, Visual Basic & C# Languages Team
Sep 2, 2014 at 12:47 PM
ADGreen wrote:
For all other situations, if a certain train of reason requires, even briefly, the existence of a setter - discard it and remember "there is no setter" :)
Thanks for detailed explanation :)

So, if there is no setter, property write #2 from my example won't compile at all, right?
class B {
  public virtual int X { get; set; }
  public virtual int Y { get; set; }
}

class C {
  public override int X { get; }
  public override int Y { get { return 42; } }

  public void M() {
    X = 42; // compiler error, because 'there is no setter'
    Y = 42; // valid assignment in C# 1.0, polymorphic setter call
  }
}
Is this right?
Sep 2, 2014 at 1:11 PM
Edited Sep 2, 2014 at 1:12 PM
ControlFlow wrote:
Is this right?
I don't have access to a VS14CPT3 instance right now to test it, but the way I understand it, X = 42 should indeed be a compiler error with the intuition that the overriding X effectively hides the virtual setter and the new X has no setter. Y = 42, on the other hand, should still be valid, since the overriding Y is not an auto-property.

But I agree that this situation is somewhat strange.
Sep 2, 2014 at 3:01 PM
Expandable wrote:
ControlFlow wrote:
Is this right?
I don't have access to a VS14CPT3 instance right now to test it, but the way I understand it, X = 42 should indeed be a compiler error with the intuition that the overriding X effectively hides the virtual setter and the new X has no setter. Y = 42, on the other hand, should still be valid, since the overriding Y is not an auto-property.
Latests 'master' build still do not implements assignment to get-only auto-properties in constructor, so there is actually nothing to play with (you can check latest 'master' branch build behavior here).
But I agree that this situation is somewhat strange.
Yep, so get-only auto-properties will have some very special hiding ability...
They can hide polymorphic setter while following the there is no setter rule :)
Sep 2, 2014 at 3:09 PM
ControlFlow wrote:
Latests 'master' build still do not implements assignment to get-only auto-properties in constructor, so there is actually nothing to play with (you can check latest 'master' branch build behavior here).
Are you sure this is working? At least the pattern matching branch doesn't seem to do what it is supposed to do. Maybe the master branch also isn't up-to-date?
Coordinator
Sep 2, 2014 at 3:16 PM
Ah-ha! I see the problem here - I misread your original question (what a difference a set; makes). My apologies.

It's important to distinguish between overriding (with the 'override' keyword) and hiding (with the 'new' keyword). There shouldn't be any hiding in this example. Any hiding that's happening implicitly feels like a bug.

Since neither B.Y nor C.Y is a getter-only auto-property the lack of a setter shouldn't impede write #2 (Y = 42) at all. By providing a body for the getter C.Y is definitely not an auto-prop. It's just a property override that overrides only the getter.

C.X looks more like a bug that falls out of the implementation and should probably be an error. In fact, overriding a read-write property with a read-only auto-property smells sufficiently suspect that we should probably disallow it. I'll follow up with the other language designers and get back to you.

-ADG
Coordinator
Sep 3, 2014 at 5:01 PM
Hey ControlFlow,

We discussed it and agreed that the override of read-write property B.X with a getter-only auto prop C.X should be a compile time error.

Thanks for raising this issue!

-ADG
Sep 3, 2014 at 9:55 PM
ADGreen wrote:
Hey ControlFlow,

We discussed it and agreed that the override of read-write property B.X with a getter-only auto prop C.X should be a compile time error.

Thanks for raising this issue!

-ADG
It doesn't make sense to be any other way. The feature is what it is and the fact that allows me to write a lot less code doesn't change its nature.
Sep 5, 2014 at 12:14 AM
ADGreen wrote:
We discussed it and agreed that the override of read-write property B.X with a getter-only auto prop C.X should be a compile time error.
Are there any cases in which an override of a virtual (non-abstract) property with any kind of auto-property should be considered sensible? Unless the base-class property was doing something weird (e.g. setter throws an exception and getter returns a constant), such an override would likely strand the base class property's backing field or worse. And if in those rare cases where it is necessary to override a virtual property with one that simply reads and writes a backing field, explicitly writing out the extra few lines of code would make clearer the intention to create a new backing field. Otherwise, if the base class was:
public virtual Moo {get; set; } = 26;
and the derived class:
public override Moo {get; set; } = 42;
it would be unclear whether the intention was really to strand the base-class backing field, or whether the intention was simply to make the default value be 42 rather than 26.