Proposal: Property-Scoped Fields #8632
Replies: 55 comments
-
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 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));
}
}
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? |
Beta Was this translation helpful? Give feedback.
-
Totally agree But I would disagree to allow name collision. It should not collide with any other field |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
@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.
|
Beta Was this translation helpful? Give feedback.
-
@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;
} |
Beta Was this translation helpful? Give feedback.
-
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). |
Beta Was this translation helpful? Give feedback.
-
In the current C# 7 preview in Local Functions (see here) when you re-declare a local variable you will get the following error:
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 😉). |
Beta Was this translation helpful? Give feedback.
-
@scottdorman, @lachbaer Im not sure I understand you because it would probably be just a 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. :) |
Beta Was this translation helpful? Give feedback.
-
@scottdorman I think that you need to create a new proposal for it, any sub topic should have its own proposal. |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
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). |
Beta Was this translation helpful? Give feedback.
-
If there is only one field needed for the primary use as backing field, I suggest an additional syntax, borrowed from C public string FirstName
{
get => _name;
set => _name = value;
} _name;
The advantage of this syntax would serve 2 purposes:
public string FirstName
{
get;
set => _name = value;
} _name = "(not set)"; or even shorter, in case expression-bodied public string FirstName
{
get;
set => value;
} _name = "(not set)"; |
Beta Was this translation helpful? Give feedback.
-
Type infering with 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. |
Beta Was this translation helpful? Give feedback.
-
/cc @CyrusNajmabadi |
Beta Was this translation helpful? Give feedback.
-
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 #133 (comment) 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 syntaxclass Foo
{
public string Bar : struct
{
string propLocal;
void localFunction()
=>throw new NotImplementedException();
public get => propLocal;
protected set => propLocal = value;
}
} The line 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 VariantsIf the parsing of public T get() => propLocal;
public void set(T value) => propLocal = value; In case you need initializers for your fields, you can either use a 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 Virtual properties that make use of inheritance can be used with |
Beta Was this translation helpful? Give feedback.
-
Yes: - public string ID : int
+ public string ID
{
+ int field;
get { return field.ToString(); }
set { field = int.Parse(value); }
} Also, it should be |
Beta Was this translation helpful? Give feedback.
-
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 Hope we can see #140 implemented first before this one, especially as it has move upvotes. |
Beta Was this translation helpful? Give feedback.
-
THe LDM voted and decided to prioritize this issue over #140. |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
@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) |
Beta Was this translation helpful? Give feedback.
-
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? |
Beta Was this translation helpful? Give feedback.
-
@michael-hawker Exactly. See more examples like this at #140 (comment). I wouldn't use the word "just" though because the compiler trick should only kick in if |
Beta Was this translation helpful? Give feedback.
-
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. |
Beta Was this translation helpful? Give feedback.
-
@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. |
Beta Was this translation helpful? Give feedback.
-
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. public event EventHandler Foo
{
EventHandler foo;
get => foo;
add => foo += value;
remove => foo -= value;
} |
Beta Was this translation helpful? Give feedback.
-
@jnm2 Actually, the problem with your solution is this: bool _active, _dead;
public event EventHandler Foo
{
EventHandler fooActive, fooInactive;
add { if(!this._dead) { if(this._active) fooActive += value; else fooInactive += value; } }
remove { if(!this._dead) { if(this._active) fooActive -= value; else fooInactive -= value; } }
} What would |
Beta Was this translation helpful? Give feedback.
-
I'd rather a That assumes that scoped fields make sense in the case of events, and I'm not really sure that they do. It's extremely niche to explicitly implement an event as it is. |
Beta Was this translation helpful? Give feedback.
-
Is there a way to vote to advance this proposition? So often I have felt guilty about creating private fields that should never be manipulated outside of Property accessors. I know it's wrong to put them in scope to trip up a maintainer, but the language currently gives us no alternative! |
Beta Was this translation helpful? Give feedback.
-
@CharlesJenkins Yes. Vote here: |
Beta Was this translation helpful? Give feedback.
-
Thanks! I hope I got the right one. |
Beta Was this translation helpful? Give feedback.
-
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:
myField
andhandler
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:
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: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:
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:
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
Beta Was this translation helpful? Give feedback.
All reactions