-
Notifications
You must be signed in to change notification settings - Fork 1k
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: field
keyword in properties
#140
Comments
This could also be useful/part of the implementation for #133. |
My opinion is that this proposal makes auto-implemented properties more difficult to read by introducing these silent backing fields despite the use of logic. With expression-bodied accessor members a chunk of the boilerplate for standard properties already disappears. The IDE eliminates much of the rest via standard code snippets. |
Suggestion:
int ASemiAutoProp
{
get;
set
{
var pcea = new PropertyChangeEventArgs(this.ASemiAutoProp, value));
OnPropertyChanging( pcea );
yield return value;
OnPropertyChanged( pcea );
}
} Then there is no need for a new And the following really looks better than with #133 public T PropertyInitialized
{
get;
set => value ?? new ArgumentNullException();
} = default(T); instead of (with #133) - a bit much of public T PropertyInitialized
{
T backingField = default(T);
get => backingField;
set => backingField = value ?? new ArgumentNullException();
} |
Instead of using a public string FirstName
{
get;
set => _name = value;
} _name;
In case expression-bodied public string FirstName
{
get;
set => value;
} _name = "(not set)"; This completely eradicates the need for a public string FirstName
{
get;
set {
var pcea = new PropertyChangeEventArgs(_name, value));
OnPropertyChanging( pcea );
_name = value;
OnPropertyChanged( pcea );
}
} _name = "(not set)"; This would also go hand in hand with #133. |
With the declaration of the field being outside of the property block that would imply that the scope of that field would be for the entire class, not for just that property. That's also consistent with the scoping of that C syntax. |
@HaloFour |
Of course specifying a getter only works, too: public string FirstName
{
get {
Debug.Assert( _name != null, "Getting name before setting it?");
return _name;
}
set;
} _name = null; or combined: public string FirstName
{
get {
Debug.Assert( _name != null, "Getting name before setting it?");
return _name;
}
set => value ?? throw new ArgumentNullException();
} _name; In the latter example, just specifying the |
I don't disagree with your whole comment, but I wanted to point out that the difficulty reading is only if you're accustomed to thinking of logic and automatic backing fields as mutually exclusive. I don't think they should be. It's not hard to learn to look for a The reason I really like this proposal is not because it enables me to write less code, but because it allows me to scope a field to a property. A top source of bugs in the past has been inadequate control over direct field access and enforcing consistent property access. Refactoring into multiple types to add that desirable scope is quite often not worth it. To that end, a Not infrequently I'm renaming constructs such as this, where private bool someProperty;
public bool SomeProperty { get { return someProperty; } private set { Set(ref someProperty, value); } } This kills two birds with one stone: 1) scope safety, 2) more DRY, less maintenence: public bool SomeProperty { get; private set { Set(ref field, value); } } |
/cc @CyrusNajmabadi |
Meanwhile some time went by. I'd like to state my opionion on the questions from the inital post. Allow both accessors to be defined simultaneously?Yes, definitely. A nice example is the sample at the end of this comment. A semi-auto-property with an implicit
Assing expression bodied setters? and Assign block body with return?I'd like to have that, because simply it looks nice and would totally fit into how "assign-to"-return expressions look. But introducing to much new behaviour and especially having the compiler and reading user to differentiate between return and non-returning bodies can be confusing. Therefore I'd go with "no" on this currently. Prohibit field keyword if not semi-auto?No, but it must not be highlighted by the IDE in that case, because it that context it is no keyword anymore. I think it is very unlikely that somebody converts a semi-auto-property to an ordinary property and simultaneously has a 'field' named field in scope. If property-scoped fields become availble shall that feature be available for semi-auto-properties as well?Yes, if any possible. SAPs allow both, getter and setter, to be defined. It would make sense to make no difference versus normal properties to restrict that feature. |
Taken from @quinmars at #681 (comment), this feature would allow devs to write a 1-liner to implement lazy initialization: public T Foo => LazyInitializer.EnsureInitialized(ref field, InitializeFoo);
private T InitializeFoo() { ... } |
@jamesqo Except using this LazyInitializer method has a downside (unless the initializer method is static) because you'll be creating a delegate instance each time the property is accessed. What you want to write is: public T Foo =>
{
if (field == null)
{
System.Threading.Interlocked.CompareExchange(ref field, InitializeFoo(), null);
}
return field;
}; And now its not really a one-liner. |
However the |
@lachbaer why? |
@yaakov-h For two reasons. First, so that you can see from the signature whether a (hidden) backing field is produced by the property. Second, to help the compiler finding out whether an automatic backing field shall be produced or not. |
First reason... I can see the value on a getter-only autoproperty like above, but on a get/set one I see little point. Second reason I don't think is necessary. If the property's syntax tree contains any reference to an undefined variable named |
The alternative is to start thinking about properties a bit differently. Every property has an explicit (synthesized) backing field, unless it is opt'ed out, because it is not needed (no |
I think a |
@jamesqo That delegate caching is for static delegates where they are not being cached in some circumstances today. It is unlikely that the |
@mattwar Then we can simply open a corefx proposal to add public class LazyInitializer
{
public static T EnsureInitialized<T, TState>(ref T value, TState state, Func<TState, T> factory);
}
public T Foo => EnsureInitialized(ref field, this, self => self.InitializeFoo()); And again, the extra allocations may be dwarfed by the initialization logic in some cases. |
Instead of introducing a new 'field' keyword you could maybe use the _ (discard) symbol. This would avoid conflicts in old code.
I think this could work. |
@sonnemaf |
Still there are some more questions to answer.
When thinking about it some time, you can come up with quite a long ruleset that must be followed. By always adding the modifier |
@lachbaer: I'd expect an existing variable, field or property named |
|
Am I only one who is extremely sceptical this will finally make it this year? |
Just wanted to point out an open source extension that is supposed to easy the pain for Go to Definition (F12) with MVVM Toolkit [RelayCommand] (among some other small quality of life improvements). The extension will automatically identify if VS opened a tab for the source generator file from MVVM Toolkit, if so it finds and opens the correct method and file (e.g. ViewModel) and closes the source generator tab. BrightXaml Extension (github/VS marketplace): https://github.com/luislhg/BrightExtensions?tab=readme-ov-file#bright-xaml Hopefully the new field should help a lot with properties/[ObservableProperty] but I think [RelayCommand] will still be very useful. |
The LDM revisited this and decided not to make changes to |
Would it still make sense to have an analyzer that suggests avoiding the use of |
@glen-84 At this point there was no sense of "maybe we'll do this later," and while anything is possible, the weight of the breaking changes was a significant enough factor that I wouldn't expect it. |
Working group meeting notes for Aug 7 are available! |
@jnm2 From the notes it looks like the discussion is largely around when the nullability feature is enabled, so I wanted to clarify something for when it's disabled. Would the proposal mean that for any value type it would still be possible for the backing field to be the nullable version? Basically, allowing the following: public int Prop1 => field ??= GetDefault1();
public SomeStruct Prop2 => field ??= GetDefault2(); It's unclear to me if those Hopefully my question makes sense. This is the first I'm hearing of these attributes in relation to this feature, so I'm still catching up. |
@seanblue Unfortunately this clarification was buried within the proposal rather than being stated up front. The proposal only applies to properties whose type is both:
We don't plan to have a provision for the runtime type of the field type to differ, e.g. The potential of doing #133 is usually the fallback we think of for cases like yours. You declare the field yourself, but still have the benefit of scoping it to the property. It might look like this: public int Prop1 { int? field; get => field ??= GetDefault(); } However, consider that even without #133, you can still do lazy loading for value types if there are sentinel values that are palatable for your purposes: public ImmutableArray<string> Values => field.IsDefault ? (field = LoadValues()) : field; public int Prop1 { get => field == int.MinValue ? (field = GetDefault()) : field; } = int.MinValue; |
@jnm2 That's a shame, but understandable. Thankfully even without this, the |
Finally, this option is available, where you access [NotNull]
public int? Prop1 => field ??= GetDefault1(); |
The proposal is updated with the working group's accepted proposal for nullability analysis of LDM 1: https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-08-14.md Updated spec section: https://github.com/dotnet/csharplang/blob/main/proposals/field-keyword.md#nullability |
Would this come in .NET 9 RC 2 release? |
Can somebody provide an answer to this question? ... As bad as it could sound, please let us hear it :) |
These kinds of questions can be as hard to answer as when any software feature will be done. |
@jnm2 since the .NET 9 RC1 had been released, have the C# 13 features been frozen? If so, we may should drop the hope for the feature in C# 13, if not, is there a timeline for the C# feature freeze? |
Sorry, I wish I could help answer that. I share your enthusiasm for the feature! |
seeing this from @jaredpar @cston on dotnet/runtime#108219 (comment) |
I know this is kinda late in the process already, but I was curious if an alternate field alias syntax was already considered (I couldn't readily find it in this ticket or the new keyword proposal so sorry if this has already been considered and rejected). I'm suggesting something like: int PropertyName as fieldAlias
{
get => fieldAlias;
set => fieldAlias = value;
} This would allow you to support all of the same scenarios and features that already described in the proposal but without the complications of adding a new keyword Compared to the field keyword proposal, it only adds a little bit of additional ceremony, and can actually be more succinct when used in small code fragments. public string? CanonName as f
{
get;
set => f = value?.ToUpper();
} |
@ebekker I'm fine, es long as C# keeps on evolving. |
@ebekker I proposed a while ago that we allow function arguments to the get and set, which is basically the same as providing an alias. Unfortunately it didn't seem to gain traction :( |
Incidentally, VB.NET does this. Property Foo As String
Get
Return _foo
End Get
Set(value As String)
_foo = value
End Set
End Property Here, |
One additional nice feature with VB was being able to add additional parameters to get/set (that needed to match). |
@ebekker It's been a while, but to my memory we did consider an ability to use an alias for the field name. One of the goals of the feature was to not have to think about a name for each property's backing field. |
I've been playing with the new preview bits and ran into some quirks. In particular, I have a property of the form:
When converting this to use field, I tried:
...but this contains some errors:
Stepping back a bit, I would really like to have BOTH a setter AND direct access to 'field' - If my property is non-nullable reference type, I will still see a null prior value when assigning from a constructor. Perhaps "PropertyName.field" would be a reasonable way to access this? I would not restrict it to constructors only. While finalizers are rare and discouraged, they are supported and need similar access to what Constructors get. Further, a case can be made for similar access in Dispose. I know I can make my non-nullable setter handle nulls, but I'm complicating the "normal case" setter to handle "abnormal case" initialization and shutdown. While I have always wanted a bevy of other property accessors, I think the variety of ways storage can be used justified some sort of direct-but-cumbersome syntax that only makes sense for constructors / initializers and the like. I did read the other proposal about full-fledged declarations in the property scope and maybe if it covers rare access to field "outside the property" that would do it, |
How are you trying to test that code? I'm running VS 17.12.0 Preview 3.0, created a .NET 9 project and set the language version to preview and that code (with |
@FrancisHogle Everything you're asking about is approved, implemented, and shipped in Visual Studio 17.12 Preview 3.0. My VS Code is also happy with the sample you provided (C# Dev Kit v1.11.14), but my Adding
|
Proposal:
field
keyword in properties(Ported from dotnet/roslyn#8364)
Specification: https://github.com/dotnet/csharplang/blob/main/proposals/field-keyword.md
LDM history:
The text was updated successfully, but these errors were encountered: