Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal: Expose auto property backing field into getter and setter scope #1497

Closed
1 of 4 tasks
anders-liu opened this issue May 5, 2018 · 8 comments
Closed
1 of 4 tasks

Comments

@anders-liu
Copy link

anders-liu commented May 5, 2018

#440
The original proposal is here: https://github.com/anders-liu/csharplang/blob/al/property-backing-field/proposals/property-backing-field.md

Property Backing Field

  • Proposed
  • Prototype: [In Progress]
  • Implementation: [Not Started]
  • Specification: [Not Started]

Summary

Expose auto property backing field into getter and setter scope through field contextual keyword.

Motivation

Consider a property with checking on its value is being set.

class Person
{
    private int _age;

    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 0 || value > 300)
                throw new System.ArgumentOutOfRangeException();

            _age = value;
        }
    }
}

For checking the value, we have to define a field on the class, which is 'globally' used between getter and setter, as well as other scopes inside a type definition. As all 'global' things are evil, developper may set invalid value to the _age field elsewhere in the class, for example:

class Person
{
    private int _age;

    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 0 || value > 300)
                throw new System.ArgumentOutOfRangeException();

            _age = value;
        }
    }

    // Far away from the above peroperty definition,
    // probably in another file of a partial class.
    void Foo()
    {
        _age = 500;  // It's definitily invalid.
    }
}

This could happen when codebase goes large and developers involved go many.

Auto property may help to hide the backing field of a property, but we lose the chance to check value.

class Person
{
    public int Age
    {
        get;
        set;  // How can we check the value being set?
    }
}

So it's necessary to involve a contextual keyword, which stands for the backing field of a property but can only be accessed in the scope of the property itself, across getter and setter accessors.

Detailed design

Define contextual auto and field keyword

Make auto and field contextual keywords inside property definition, where auto indifies a property will use exposed backing field, and field stands for the auto-generated backing field of a property.

class Person
{
    // The `auto` modifier tells compiler that
    // this property needs auto-generated backing field.
    public auto int Age
    {
        // `field` stands for the auto-generated
        // backing field for the `Age` property.
        get { return field; }
        set
        {
            if (value < 0 || value > 300)
                throw new System.ArgumentOutOfRangeException();

            field = value;
        }
    }
}

The auto should be a new contextual keyword, which is required because field will come into conflict with any class member named field.

The field has already been a contextual keyword, though it's rarely seen in documents. See Attribute specification in C# specification. That means the field keyword won't appear in property definition context, so we can reuse it. And this also help to avoid involving one more new keyword.

Backward compatible considerations

With the newly defined auto keyword, this change won't break existing code. Consider the following code:

class Person
{
    // Already have a `field` field defined.
    private int field;

    public int Age
    {
        // Without `auto` modifier, the `field` is bound to `field` field.
        get { return field; }
        set
        {
            if (value < 0 || value > 300)
                throw new System.ArgumentOutOfRangeException();

            field = value;
        }
    }
}

Without auto modifier in the property definition, the field will be explained as a usual identifier, and will be bound to the class member field, which is a field here.

With auto modifier, the field inside a property will be explained as keyword and be bound to the backing field of the property, while you can still use this.field to reference a class field.

class Person
{
    private int field;

    public auto int Age
    {
        // The class `field` field is hidden in scope of this property.
        get { return field; }
        // But you can use `this.field` to referece it.
        set
        {
            if (value != this.field)  // Check equality with class field
                field = value;  // Set backing field.
        }
    }
}

Semi-auto properties

Mostly, the getter of a property contains only one return field; statement, which is so boring. So we can leverage the triditional auto property syntax for only one of the accessors.

class Person
{
    public int Age
    {
        // Auto getter, emit `return field;` same as auto property.
        get;
        set
        {
            // Do value checking here.
            field = value;
        }
    }
}

And, may not that common, we can also define auto-setter-only property:

class Person
{
    public int Age
    {
        get
        {
            // Do some calculations before return a value.
            return field * 10;
        }
        // Auto setter, emit `field = value;` same as auto property.
        set;
    }
}

Note, since existing auto implemented property feature doesn't allow 'semi-auto' property, we can ommit auto modifier here, it's safe and won't break any existing code.

Drawbacks

TBD

Alternatives

Option 1: define UseBackingFieldAttribute class

Instead of involving new auto keyword, we can define a new UseBackingFieldAttribute class, and use this attribute on the property which needs auto-generated backing field:

using System;

class Person
{
    [UseBackingField]
    public int Age
    {
        get { return field; }
        set
        {
            if (value < 0 || value > 300)
                throw new ArgumentOutOfRangeException();

            field = value;
        }
    }
}

This option involves new class definition, and emits attribute to the output assembly, which is unnecessary for runtime, and require the output assembly depends on the last version of .NET Framework.

Option 2: definePropertyBackingFieldAttribute class

We can also define a new PropertyBackingFieldAttribute class, and use this attribute on a field, to inform compiler that this field can only be accessed through property with the given name.

using System;

class Person
{
    [PropertyBackingField("Age")]
    private int _age;

    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 0 || value > 300)
                throw new System.ArgumentOutOfRangeException();

            _age = value;
        }
    }
}

But developer probably forget to add this attribute to the field. And again, the new class definition requires output assembly depends on last version of .NET Framework.

Unresolved questions

n/a

Design meetings

n/a

@ufcpp
Copy link

ufcpp commented May 5, 2018

#133

@jnm2
Copy link
Contributor

jnm2 commented May 5, 2018

For the field keyword and semi-auto properties, see #140.

@Unknown6656
Copy link
Contributor

Unknown6656 commented May 5, 2018

As I mentioned in #1336 (comment):
The following proposals/issues have this feature mentioned directly or indirectly:
#1420
#1336
#1290
#1240
(#1059)
#626
#318
#140
#133
[and there are probably even more]

Do please consider closing this issue if at least one of the other ones would solve it in order to keep the amount of duplicates low.

@quinmars
Copy link

quinmars commented May 6, 2018

I also don't like duplicate issues, but I think this one significantly differs to the original proposal of #140 (and that's the closest proposal of the list). Well, you could say it is a duplicate to @BobSammers' #140 (comment) and #140 (comment). But those comments are hard to reference.

@anders-liu you may add a paragraph which mention #140 and work out the differences.

I'm undecided if I prefer the modifierless #140 or #1497. I'm also not sure if I like the auto keyword. @BobSammers proposed the keyword backed which would be more concrete. Personally I like the keyword ``property`. On the long run that keyword could bring a bit more symmetry to events and properties.

I don't like option 1. IMO an attribute should no change the language grammer.

@TheUnlocked
Copy link

TheUnlocked commented May 10, 2018

I think option 2 is the best idea here. auto is used by c++ as var is used by c#, so using auto for this purpose would just add confusion for people who use both languages. backed is a good, and more descriptive, alternative, though I'm still not totally sure how I feel about adding a whole new keyword just to avoid a single backwards-compatibility violation. Using an attribute on the private field really seems like it would be the best way to go unless a potential new keyword would have a use in more than one case.

Another alternative, if the use of a keyword is really desired, would be a using in the property declaration. For example:

public int Age
{
    using { int _age = 0; }
    get { return _age; }
    set
    {
       if (value < 0 || value > 300)
           throw new System.ArgumentOutOfRangeException();

        _age = value;
    }
}

@BobSammers
Copy link

Is there any advantage to using (above) over just allowing variables declared in the ordinary way but scoped to the property?

public int Age
{
    int _age = 0;

    get => ...;
    set => ...;
}

if the use of a keyword is really desired

I can't imagine anyone wants to introduce a keyword for the sake of it, just if it eliminates a breaking change to existing code (and only then if it's considered to be a significant problem).

For me, this family of proposals (as it seems to have become) has two goals:

  1. Narrow the scope of backing fields to the property body
  2. Remove the need to pick two names for a single property. Two identifiers generates:
    1. Boilerplate (for the backing field definition)
    2. Confusion (having two, usually similarly named, identifiers in scope together - good potential for bugs due to referencing the wrong identifier, especially if the backing field is scoped outside the property)
    3. Refactoring hassle (remembering to change two names when renaming a property).

Scoping a field to the property with either a using statement or just a normal field declaration solves the first issue but not the second.

Another idiom that is made a little easier by avoiding the need to explicitly declare the backing field is the single-line property, such as this example from one of my comments in #140:

public int SomeProperty => field = isDirty ? RecalcProperty() : field;

There is nowhere obvious in a line like this to declare a backing field scoped only to the property.

For these reasons, in #140 I have advocated an approach that uses field as an identifier and (as @quinmars has already pointed out) the modifier backed on the property declaration to avoid breaking existing code in which field has been declared as an identifier. This is broadly similar to the main suggestion in the original post here. The above line would be:

public backed int SomeProperty => field = isDirty ? RecalcProperty() : field;

I agree with @quinmars that the language shouldn't be altered by the use of attributes, so I wouldn't go along with their use as described in the original post's "alternatives" section.

@anders-liu
Copy link
Author

anders-liu commented May 18, 2018

Thank you guys for pointing out the similar ones; I actually did a search before writing this proposal but I didn't know which keywords should I use so I didn't see my ideal proposal.

(The 'original proposal' is also mine, so there isn't any difference between them; I just posted it in a wrong way.)

But yes, the ones you pointed out definitely fit my expectation, I don't care the detailed syntax change, I only want to control over the auto generated backing field of a property.

So closed this one; thank you guys!

@Unknown6656
Copy link
Contributor

Considered in https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-04-01.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants