-
Notifications
You must be signed in to change notification settings - Fork 1k
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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 |
The .NET SDK 9.0 can now be downloaded, and it supports building with |
Does the 17.13 P1 milestone effectively mean .NET SDK 9.0.200? Or will it be back ported to 9.0.1xx? |
Still get error when building with .NET 9 SDK, any plan for the fix? and which nuget package version could be expected to include the fix? |
I installed latest Visual Studio 17.12.0 to try .NET9 I can compile properties with
Will this be available in C#14? or in C#13? |
This would be GA in C# 14, preview feature for now |
The "FieldInfo field" does not have an error. (bad) I would expect an error on the declaration of a local variable named 'field'. |
@UniqeId6542 |
Yes. But that does NOT produce the error. The NEXT line produces the error - try it yourself. |
@UniqeId6542 but the warning on
If I change the line to: this.foo = @field.GetValue("hi"); Then the error, as expected, is:
|
This comment has been minimized.
This comment has been minimized.
Consider:
I can fix the error on the second line by changing |
@UniqeId6542 Actually, many keywords can be used as identifier names. It's only no -contextual keywords that can't. |
This was explored and found to have way too much of an impact to existing code, so the team decided to take a step back and treat it as a contextual keyword instead that is treated as a keyword only when an identifier of the same name is not in scope. This is how C# has approached adding most new identifiers, to reduce breaking changes, and while the team was open to taking a harder stance here the data showed that it would not be a good idea. |
@UniqeId6542 please file issue at dotnet/roslyn. This issue is for discussion around the design of this feature. |
Can you name one contextual keyword where declaring it does not require the '@' prefix but using it does require the '@' prefix?
Perhaps it was considered. But I disagree with the decision. Since using a local variable named 'field' requires the '@' prefix, I don't see how it is more of a breaking change to also require the declaration to use the '@' prefix. (I don't expect people to declare a local variable but not use it.) |
You can add taht restriction if you want in your own code through the use of custom analyzer. The language doesn't require it, so we didn't add such a restriction. |
Yes. Here's an example with // Declared without @
int from = 0;
var v = from x in numbers
// Requires @ here
where x == @from
select x.ToString(); |
You can declare it. We don't require The same is true for |
The data showed it was tremendously more breaking. See https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-07-15.md#field-keyword for the meeting decision on this. |
@UniqeId6542 That's what we tried first, but GitHub data showed it broke too much code, so we had to dial it down. |
A bit of a tangent, but has the team considered creating auto-fixers like |
I believe you, but I am curious what cases are people declaring a local variable "field" in a property body then not accessing it? Or were people already using a @ in uses of the field but not the declaration? |
@Boomkop3 what else do you see synthesized there? All i see is a synthesized backing |
@CyrusNajmabadi I am seeing |
the property initalizer " = null!" is not part of the setter, it will be set as the initial value of the backing field. |
Yes. That assigns to the backing field (just like a normal auto prop today). For example: That didn't change with |
@CyrusNajmabadi ah, you're right, that's just for initialization. It's quite obvious now that I look at it again. Thank you! |
This issue was moved to a discussion.
You can continue the conversation there. Go to discussion →
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: