dotnet / csharplang

The official repo for the design of the C# programming language
11.61k stars 1.03k forks source link

Proposal: Property-Scoped Fields #133

Closed bondsbw closed 1 week ago

bondsbw commented 7 years ago

Property-Scoped Fields

(Ported from dotnet/roslyn#850)

Summary

Allow fields to be scoped within the accessor body of a property or event declaration.

Motivation

Properties often encapsulate constraints on field access or ensure that certain behaviors are invoked. Sometimes it is appropriate for all field access to be performed through the property, including those from within the same class.

These constraints may be unclear or forgotten, leading to subtle bugs during maintenance which are often difficult to diagnose.

Scoping fields to the body of their property/event would allow the designer to more effectively communicate this intent to encapsulate.

Detailed design

Property-scoped fields may be defined above the accessor list within the body of a property or event:

public string MyProperty
{
    string myField;

    get { return myField; }
    set
    {
        myField = value;
        NotifyOfPropertyChange(nameof(MyProperty));
    }
}
public event EventHandler MyHandler
{
    EventHandler handler;

    add
    {
        handler += value;
        Logger.Log("Added handler");
    } 
    remove
    {
        handler -= value;
        Logger.Log("Removed handler");
    }
}

myField and handler are encapsulated within their respective property/event. In each case, both accessors may directly reference the fields. Nothing outside of the property/event scope has direct access to those fields.

Implementation comments from @CyrusNajmabadi:

First, this is just state that's available to this single property that the value is scoped to. In that regard, it's similar to a 'local'. Just a 'local' that is available to all the accessors. As such, this is how i've been designing things from teh language level:

  1. You can have any number of property locals declared with the property.
  2. The property locals can come before/after/interspersed with the actual property accessors.
  3. The property locals are scoped to that property alone. Other members of the class/struct cannot interact with them.
  4. Subclasses can't interact with them (including overrides of the property).
  5. 'var' can be used for these property locals.
  6. Indexers should also be able to use these sorts of property locals.

Now (while this is out of the scope of the actual language change), here's how i would expect to emit them.

  1. These would simply be fields within the same class/struct that the property/indexer is contained within.
  2. The fields would be private.
  3. The fields would be named in such a way that they could never collide with an actual class member. likely something wonky <>_propname_localname

Alternatives

Automatic backing field

This implementation would be an extension of automatic properties. The backing field could be accessed through a keyword field, but only within the scope of the property/event:

public string MyProperty
{
    get { return field; }
    set
    {
        field = value;
        NotifyOfPropertyChange(nameof(MyProperty));
    }
}

This would not allow multiple fields to be encapsulated in the same property.

dotnet/roslyn#7614 has a similar proposal with a different keyword ($state).

Auto-property syntax for custom setter/getter logic

dotnet/roslyn#1551

Semi-auto-properties with setters

dotnet/roslyn#8364

Class-scoped blocks for fields / explicit field usage

dotnet/roslyn#12361

Unresolved questions

A list of questions from @CyrusNajmabadi:

  1. Allow attributes on these locals? It wouldn't be hard to support, and there could be value in putting attributes on the final fields emitted. This does leak through though the implementation of how we deal with property locals.

2. Allow these for events? Yes

  1. How are we going to handle the syntax changes here in our API. It's non trivial to modify the syntactic constructs here in a non-breaking fashion.
  2. We've added 'local functions' to C#7. Should we allow "property local functions" as well?
  3. Should we allow other 'local' constructs? We could have local methods/types/etc. within a property. Technically it would all be possible.

Syntax

It was noted that adding fields above accessors or below accessors (but not interspersed) could be an easier change to the existing compiler design. @lachbaer provided a potential design which would not be a breaking change to the API:

<!-- this Node hase been added, it looks exactly like "FieldDeclarationSyntax" -->
<Node Name="PropertyLocalFieldDeclarationSyntax" Base="BaseFieldDeclarationSyntax">
    <Kind Name="PropertyLocalFieldDeclaration"/>
    <Field Name="AttributeLists" Type="SyntaxList&lt;AttributeListSyntax&gt;" Override="true">
      <PropertyComment>
        <summary>Gets the attribute declaration list.</summary>
      </PropertyComment>
    </Field>
    <Field Name="Modifiers" Type="SyntaxList&lt;SyntaxToken&gt;" Override="true">
      <PropertyComment>
        <summary>Gets the modifier list.</summary>
      </PropertyComment>
    </Field>
    <Field Name="Declaration" Type="VariableDeclarationSyntax" Override="true"/>
    <Field Name="SemicolonToken" Type="SyntaxToken" Override="true">
      <Kind Name="SemicolonToken"/>
    </Field>
  </Node>

  <Node Name="AccessorListSyntax" Base="CSharpSyntaxNode">
    <Kind Name="AccessorList"/>
    <Field Name="OpenBraceToken" Type="SyntaxToken">
      <Kind Name="OpenBraceToken"/>
    </Field>
    <!-- next line has been added -->
    <Field Name="LocalFields" Type="SyntaxList&lt;PropertyLocalFieldDeclarationSyntax&gt;" Optional="true" />
    <Field Name="Accessors" Type="SyntaxList&lt;AccessorDeclarationSyntax&gt;"/>
    <Field Name="CloseBraceToken" Type="SyntaxToken">
      <Kind Name="CloseBraceToken"/>
    </Field>
  </Node>

Field name collisions

Should a property-scoped field be allowed to have the same name as another class member?

Should local identifiers within accessors be allowed to have the same name as a property-scoped field?

Design Meetings

scottdorman commented 7 years ago

Overall, I like this. Would it be reasonable to implement this so that we have both property scoped fields and also the automatic backing field?

For simple cases, such as properties that just need to implement INotifyPropertyChange we don't need to declare the field ourselves:

public string MyProperty
{
    get { return field; }
    set
    {
        field = value;
        NotifyOfPropertyChange(nameof(MyProperty));
    }
}

For more complex scenarios, we can declare our own local property scoped field:

public string MyProperty
{
    string myField;

    get { return myField; }
    set
    {
        myField = value;
        NotifyOfPropertyChange(nameof(MyProperty));
    }
}

Should a property-scoped field be allowed to have the same name as another class member?

Yes, I think so. Would this really be any different than having a variable defined inside a method having the same name as variable defined outside the method?

Thaina commented 7 years ago

Totally agree

But I would disagree to allow name collision. It should not collide with any other field

bondsbw commented 7 years ago

this. can be used to access class members which are hidden by locals. Presumably it would also be used for property-scoped field access. But if name collisions were allowed, then it would be possible to have property-scoped fields which are inaccessible when hidden by locals (or, a different keyword or syntax would be needed to access them).

iam3yal commented 7 years ago

@scottdorman I guess that for simple scenarios and probably even complex ones you could use source generators for this so you wouldn't have to do that manually.

[NPCGenerator]
public string MyProperty { get; set; }
lachbaer commented 7 years ago

@eyalsk But not everybody has those generators by hand, especially when reading foreign code.

I would also disagree to allow name collision, with the exception of modifying a local 'new'.

public string MyProperty
{
    string myField;

    get => {
        new int myField;
        /* why should you do this, it doesn't make sense, but it assures that you know what you do */
    }
    set => myField = value;
}
scottdorman commented 7 years ago

All good arguments, I think, for not allowing the name collisions. I'm changing my vote to "a property-scoped field should not be allowed to have the same name as another class member."

@eyalsk Yes, a code generator would work but not everyone can (or wants to) use them. Personally, I think access to the backing field of an autoprop should have been there from the start. Also, code generators aren't necessarily reasonable for all scenarios where I'd want access to the backing field. A few examples of that is doing complex validity checks on the value before setting it or setting the property has side-effects of setting some other read-only properties (think about splitting a full name property up in to first and last name).

lachbaer commented 7 years ago

In the current C# 7 preview in Local Functions (see here) when you re-declare a local variable you will get the following error:

A local or parameter named 'localVariable' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter (ERR 0136)


static void Main(string[] args)
{
var localVariable = 12;
        void LocalFunction()
        {
            var localVariable = 12;  // ERROR, duplicate declaration
        }
    }

The same error could be issued when using a _property scoped local_ - where the whole property is the _enclosing scope_, thus eliminating any further confusions or needs for extra keywords to access them (the outer variable, not the confusion :wink:).
iam3yal commented 7 years ago

@scottdorman, @lachbaer Im not sure I understand you because it would probably be just a Install-Package <SomePackage> away.

I like the original proposal but I think that the amount of ways that you can construct properties today is overwhelming so going further with it and add more ways is just taking properties to the extreme. :)

iam3yal commented 7 years ago

@scottdorman I think that you need to create a new proposal for it, any sub topic should have its own proposal.

scottdorman commented 7 years ago

@eyalsk Looks like @lachbaer already created the proposal for it. I brought it up here because I was responding to the first two listed alternatives in this proposal, but I agree it needs to be a separate proposal.

HaloFour commented 7 years ago

I think it's fine for two property-scoped fields to share the same name, but not for a property-scoped field to share the same name as another top-level member:

// good
public class FizzBuzz {
    public string Fizz {
        string field;
        get => field;
        set => field = value;
    }

    public string Buzz {
        string field;
        get => field;
        set => field = value;
    }
}

// bad
public class FizzBuzz {
    private string field;
    public string Fizz {
        get => field;
        set => field = value;
    }

    public string Buzz {
        string field; // CS0102
        get => field;
        set => field = value;
    }
}

I'm not a fan of partially-auto-implemented properties. With expression-bodied accessors much of the syntactic boilerplate is already eliminated. Having to declare an explicit fields seems reasonable, especially in the case of dealing with calculated values (e.g. name parsing).

lachbaer commented 7 years ago

If there is only one field needed for the primary use as backing field, I suggest an additional syntax, borrowed from C struct:

public string FirstName
{
    get => _name;
    set => _name = value;
} _name;

_name is by definition private to it's type (maybe unless directly prepended by an access modifier?). OR, appropriate for this proposal, just scoped to the corresponding property.

The advantage of this syntax would serve 2 purposes:

  1. no need to explicitly state the type of the property (again)

  2. This would also be in favor of #140, Semi-Auto-Properties, because that named backing field would expunge the need for a field keyword:

    public string FirstName
    {
    get;
    set => _name = value;
    } _name = "(not set)";

    or even shorter, in case expression-bodied set of semi-auto-properties expect a value being returnd to assign to the backing-field:

    public string FirstName
    {
    get;
    set => value;
    } _name = "(not set)";
lachbaer commented 7 years ago

Type infering with var for the property-scoped fields should be available as well.

property BarClass Foo
{
    var _field; // automatically initialized with `default(BarClass)`
    get { }
    set { }
}

Like for other fields, implicit typing by initialization is prohibited

property BarClass Foo
{
    var _aCar = new Car(); // Error CS0825  The contextual keyword 'var' may only appear within a local variable declaration or in script code
    get { }
    set { }
}

I think that's intuitive, because under the hood those variables are compiler generated class fields.

gafter commented 7 years ago

/cc @CyrusNajmabadi

lachbaer commented 7 years ago

It seems to turn out that the scoping is harder to achieve than it looks, especially when it comes to inheritance.

Update (27 Mar 2017): revoked; see https://github.com/dotnet/csharplang/issues/133#issuecomment-297770415

As there also was the discussion, whether other members despite fields should also be allowed, and whether declarations must appear at the beginning of the property or can be done anywhere I came up with the idea of sub-classing properties. That would give you much more control over those issues.

Proposed syntax

class Foo
{
    public string Bar : struct
    {
        string propLocal;
        void localFunction()
            =>throw new NotImplementedException();

        public get => propLocal;
        protected set => propLocal = value;
    }
}

The line public string Bar : struct starts like an ordinary property, but then tells the compiler to treat the following block basically like a struct definition. This will help the compiler to distinguish betwen the old classic property syntax and the extended one.

The above could would be translated to something like:

class Foo
{
    // <Bar>k... are internal identifiers, not directly accessible by user code
    private struct <Bar>k__propertyStruct<T>
    {
        T propLocal;
        void localFunction()
            => throw new NotSupportedException();
        public T get() => propLocal;
        public void set(T value) => propLocal = value;
    }
    private <Bar>k__propertyStruct<string> <Bar>k__backingField;

    public string Bar
    {
        get => <Bar>k__backingField.get();
        protected set => <Bar>k__backingField.set(value);
    }
}

On my old Core-i5 the performance impact of "struct-typed-properties" is ~70% (1 sec vs. 1.7 secs) when used on outer struct, and ~40% (5 secs vs. 7 secs) when used on outer classes. I created 10 billion Point structs/classes.

Variants

1. If the parsing of get/set { } and get/set => is too complicated, getter and setter could be declared the classical method way. The compiler ensures that at least either method is available with the right signature.

public T get() => propLocal;
public void set(T value) => propLocal = value;

2. In case you need initializers for your fields, you can either use a Bar : class instead of struct - but the performance impact is huge! Or define an initializer that calls an appropriate struct constructor

public string Bar : struct
{
    string propLocal;
    void localFunction() =>throw new NotImplementedException();
    public get => propLocal;
    protected set => propLocal = value;

    public Bar(string initvalue)
    {
        // do all initializations here
    }
} = ""; // initializer calls constructor

3. Virtual properties that make use of inheritance can be used with Bar : class also.

bondsbw commented 7 years ago

@lachbaer Why not just use a nested type?

Nested types exist (in part) to provide a lot of additional encapsulation, and they do so with additional overhead. There is performance overhead related to the additional type, and there is cognitive overhead related to nesting all those things.

A goal of this proposal is to hit a sweet spot where we can provide a bit of additional encapsulation without all that overhead. Your addition goes beyond that purpose, and I would prefer to separate it from this proposal. Property-scoped fields can exist without property-scoped methods/events/constructors/etc. (at least for the first release of the feature).

bondsbw commented 7 years ago

To clarify, I not only believe that "property-scoped everything" is a separate proposal from this, but that your specific suggestion of adding : struct and : class is a separate proposal from both.

lachbaer commented 7 years ago

@bondsbw The proposed construct would allow to support every aspect of property scoped fields, even offer more possibilities, while simultaneously keeping the API compatible.

Advantages of this versus "simple" property scoped fields in this respect are:

  1. Fields can be defined at any point in the block
  2. "Property Local functions" can easily be realized
  3. Keeping the (Roslyn-) API compatible for property-scoped-members is hard if not impossible. My proposal would allow to create a completely new API for SourceStructPropertyDeclartion and avoid conflicts with the "old" existing API.

And maybe it is more easy to realize for the compiler team?

lachbaer commented 7 years ago

@bondsbw

Why not just use a nested type?

Actually that is where this idea initiated. But the overhead is quite a bit and does not prevent to have the type instance field visible within the whole class - and there we're back again on the property scopes πŸ˜ƒ I then simply merged those two ideas and took into account the scoping issues Cyrus had when starting on this proposal.

bondsbw commented 7 years ago

Fields can be defined at any point in the block

This isn't a feature; it is a design decision (one which is still not finalized). If the implementation doesn't allow mixing field declarations and accessors, then it's because that was considered better than the alternative.

"Property Local functions" can easily be realized

They are already realized in nested types, can't get much easier than that.

Keeping the (Roslyn-) API compatible for property-scoped-members is hard if not impossible. My proposal would allow to create a completely new API for SourceStructPropertyDeclartion and avoid conflicts with the "old" existing API.

Your suggestion also requires defining a new kind of block that is almost but not quite like a class/struct (it has two accessors interleaved). Explicit getter/setter methods would fix that, but that is a less valuable proposal IMHO.

But the overhead is quite a bit and does not prevent to have the type instance field visible within the whole class

Sure it can, say you have this which is equivalent to your scenario above:

class Foo
{
    private struct BarInfo
    {
        public string Value { get; private set; }
        public BarInfo(string value) => Value = value;
        void localFunction() =>
            throw new NotSupportedException();        
    }
    private readonly BarInfo _bar = new BarInfo("asdf");
    public string Bar => _bar.Value;
}

If you try to set Bar somewhere else inside Foo you get an error:

    public void DoSomething()
    {
        Bar = "qwerty"; // Error CS0200 (read only)
        _bar.Value = "qwerty"; // Error CS0272 (inaccessible)
        _bar = new BarInfo("qwerty"); // Error CS0191 (readonly field assignment)
    }

That said, this is a bit less fluid than your suggestion so I say add it as a new proposal. Maybe it will gain traction. I still like this proposal to stand on its own.

lachbaer commented 7 years ago

I think that : class is a bit overshooting. When you are that far that you really need fancy class features like polymorphy, etc. you should think about a vast refactoring of the code. Or just sub-class your property as @bondsbw proposed in the first place. With semi-auto-properties even no extra instance holding field is necessary.

Because initializing fields with initializers is not possible within structs, I proposed a forced nullable struct over in #99. That construct could be used here as well for T Property : struct.

lachbaer commented 7 years ago

Poll

I only need this feature for a few property-scoped fields. 'private' scope to the property is sufficient for my purposes. In case of inheritance, e.g. 'protected' fields, I would rather revert to a class scoped field anyway.

Multiple votes allowed.

lachbaer commented 7 years ago

@stepanbenes What shall πŸŽ‰ mean? πŸ˜ƒ

@bondsbw Would delegating to a (nested) class/struct help to satisfy your needs? If so, what do you think of a "delegating syntax"

public T Foo : NestedBar { get; set; }    // were NestedBar : struct, class

that'll stand for the following "huge boilerplate" πŸ˜‰

private T _foo_backingField = new NestedBar();
public T Foo { 
    get => _foo_backingField.get();
    set => _foo_backingField.set(value);
}

Addendum: here semi-auto-properties would come in handy to avoid boilerplates.


public T Foo { get => field.get(); set => field.set(value); } = new NestedBar();
lachbaer commented 7 years ago

Concerning my "proposed" syntax for this delegating in https://github.com/dotnet/csharplang/issues/133#issuecomment-295357856 I'm making this update:

Considering the amount of saved "boilerplate" and simultaneously weighing the costs, this is rather a case for a code generator (#107) than for a compiler extension. With semi-auto-properties even a code generator is overshooting the target.

bondsbw commented 7 years ago

@lachbaer I may have misinterpreted your statement:

I only need this feature for a few fields.

Is this to mean "I do not need to use many property-scoped fields" or "I do not need to scope more than than a few fields inside any single property"?

lachbaer commented 7 years ago

@bondsbw I have updated the text of the poll to hopefully be more expressive.

bondsbw commented 7 years ago

In that case I stand by my vote.

Using a nested class to represent one or two fields seems like overkill; one point of this proposal is that a nested class is not a worthy tradeoff for encapsulation, and I would like to fix that.

I don't particularly care for the getter and setter method syntax either.

bondsbw commented 7 years ago

@lachbaer Wait, now :thumbsup: means I don't want to use property-scope fields often, and :thumbsdown: means I want the kitchen sink?

You have no option for "I want to use this feature, as simply proposed, in many places."

lachbaer commented 7 years ago

@bondsbw Updated the text again. If you have suggestions how to rephrase it, they are welcome :-) Or vote for πŸ˜• and explain your wish, if you like.

bondsbw commented 7 years ago

I'll vote for :thumbsup: based on that change. But if this is implemented I do plan to transition most backing fields I write into a property scope.

jnm2 commented 7 years ago

I prefer https://github.com/dotnet/csharplang/issues/140 to this because it covers just about all my use cases and is cleaner.

// field is a contextual keyword just like value
public bool IsEditing { get; set => RaisePropertyChanged(ref field, value); }
bondsbw commented 7 years ago

Note that this proposal does not conflict with #140. They represent different use cases.

This proposal is useful for cases where the type of the backing field is different, or if there are multiple backing fields, e.g. initialization:

public string DatabaseServer
{
    string _databaseServer;
    bool _isInitialized;
    get => _isInitialized ? _databaseServer : throw ...;
    set => !_isInitialized ? _databaseServer = value : throw ...;
}
jnm2 commented 7 years ago

@bondsbw Certainly, but use cases do overlap and I'd prefer the overlapping cases be addressed with a cleaner style than the cases which actually deserve explicitly named and typed scoped fields.

jasonkuo41 commented 5 years ago

Hate to say this, but this is proposal is posted in 2017 and it's now 2019. People seem to be proposing the same concept again and again and this issue keeps getting referenced, but there seems to be no update with whether this is going to be implemented or rejected.

IMO, this is an important feature for C# as pretty much everyone has their private fields and public fields having similar names and they can easily pass compiler check if wrongly used. This kind of typo error can only either be identified in runtime or unit tests.

I weighted this feature over lots of the new features added in newer versions of C#.

HaloFour commented 5 years ago

@jasonkuo41

Hate to say this, but this is proposal is posted in 2017 and it's now 2019. People seem to be proposing the same concept again and again and this issue keeps getting referenced, but there seems to be no update with whether this is going to be implemented or rejected.

Many of the features being added to C# 8.0 were proposed/discussed well before 2017. The team has a very long backlog and language design can be a very lengthy process. The vast majority of the non-championed proposals on this repo have not been outright rejected. I agree that this is a bit of a problem with the process, although I'm not sure that there's anything that can be done about it. There are probably over a decades worth of work that could be driven just by community proposals.

IMO, this is an important feature for C# as pretty much everyone has their private fields and public fields having similar names and they can easily pass compiler check if wrongly used. This kind of typo error can only either be identified in runtime or unit tests.

Or analyzers, which you can implement today if this is a concern.

I weighted this feature over lots of the new features added in newer versions of C#.

Everyone has their own priorities, but ultimately the LDM decides what features are prioritized for work. These semi-auto-property proposals need a champion before they even appear on that list.

bjorg commented 5 years ago

Would love to see this. For more use cases, I filed a duplicate issue #2913, before someone redirected me here.

michael-hawker commented 4 years ago

Was definitely just hitting this type of issue where I want to encapsulate the backing field in order to tie behavior changes to the update of the property from within a class. Think this or #140 would work for this specific scenario.

Definitely feel like to be in the spirit of properties and C# the syntax should be as simple and straight-forward as possible without much extra boilerplate. Just being able to declare a property-scoped field as in the original proposal and/or having access to a field value representing the current backed field would be great!

bjorg commented 4 years ago

Requiring the backing field to be declared explicitly inside the scope of the property would be minimally invasive and allow for the backing field type to be different from the property type or even have multiple backing fields if need be.

For example:

public int GetValue {
    Lazy<int> _myLazyValue = new Lazy<int>(() => 42);

    get => _myLazyValue.Value;
}
ghost commented 4 years ago

PropertyType != FieldType

Format: PropertyType PropertyName : FieldType

For example:

        public string ID : int
        {
            get { return field.ToString(); }
            set { field = int.Parse(value); }
        }
Happypig375 commented 4 years ago

@SinDynasty For cases like that, don't use semi-auto properties, just use property-scoped fields. That will be much clearer.

jnm2 commented 4 years ago

Yes:

-       public string ID : int
+       public string ID
        {
+           int field;
            get { return field.ToString(); }
            set { field = int.Parse(value); }
        }

Also, it should be Id rather than ID since 'ID' is a compound word and not an acronym. https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/capitalization-conventions#capitalizing-compound-words-and-common-terms

michael-hawker commented 4 years ago

I could see use for this, but feel like I'd be using #140 all the time in comparison. Especially for things like MVVM, we have a new .NET Standard MVVM library coming soon... Just having an implicit field access would be helpful.

Hope we can see #140 implemented first before this one, especially as it has move upvotes.

CyrusNajmabadi commented 4 years ago

Hope we can see #140 implemented first before this one, especially as it has move upvotes.

THe LDM voted and decided to prioritize this issue over #140.

michael-hawker commented 4 years ago

@CyrusNajmabadi is this in the meeting notes somewhere? I didn't see it in the ones from Aug 24th. Did they mention why they prioritized this one over #140 when 140 has twice the votes?

The last note I see on the topic is in the July 13th notes where they say they're designing both proposals.

jnm2 commented 4 years ago

@michael-hawker Yes, see the notes from the second LDM discussing this feature: https://github.com/dotnet/csharplang/blob/master/meetings/2020/LDM-2020-06-24.md#Property-Enhancements (discussion issue: #3615)

michael-hawker commented 4 years ago

Thanks for the links @jnm2. Good to see the discussion is still talking about both proposals and how they interact.

At least going with the more complex proposal first also may mean that during implementation it's found to get 140 easier as well. In some ways if 133 is supported, then 140 is just a compiler trick which turns this shorthand:

public SomeType SomeProperty { get; set => Set(ref field, value); }

into this:

public SomeType SomeProperty { SomeType field; get => field; set => Set(ref field, value); }

Right?

jnm2 commented 4 years ago

@michael-hawker Exactly. See more examples like this at https://github.com/dotnet/csharplang/issues/140#issuecomment-649871912. I wouldn't use the word "just" though because the compiler trick should only kick in if field can't be bound to any existing name in scope. In order to collect all the effective associated fields for a class, you have to bind property accessor bodies. This is a new kind of thing for the compiler to have to deal with.

HaloFour commented 4 years ago

I think one thing that could make this proposal much more useful would be to lift the limitation on the property-scoped field initializer and allow it to access instance members. That would enable scenarios like this following:

public int LazyProp {
    Lazy<int> lazy = new Lazy<int>(ExpensiveComputation);
    get => lazy.Value;
}

Even better if the property-scoped field could be treated like a static local of sorts and can have its type inferred by the initializer:

public int LazyProp {
    var lazy = new Lazy<int>(ExpensiveComputation);
    get => lazy.Value;
}

This also feels like a stepping-stone towards delegated properties and could simplify a number of AOP-like scenarios.

bondsbw commented 4 years ago

@HaloFour I like it, but it seems like the compiler could run into a dependency cycle if multiple property-scoped initializers reference each other’s property getters.

TahirAhmadov commented 3 years ago

How would the event be raised in this scenario? If an event defines the add/remove, the current convention is that the underlying field is used to raise the event, not the event itself. I think a solution would be to add a new accessor to events - get:

public event EventHandler Foo
{
  EventHandler foo;
  get => foo;
  add => foo += value;
  remove => foo -= value;
}