- Notifications
You must be signed in to change notification settings - Fork1.1k
Feedback on primary constructors#7667
-
C# 12 introduces primary constructors for non-record types:https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12#primary-constructors What else would you like to see and what are the important scenarios to you? A gesture that creates properties in non-record types? Support for immutability in primary constructor parameters? Something else? |
BetaWas this translation helpful?Give feedback.
All reactions
👍 14😕 2❤️ 1
Replies: 17 comments 63 replies
-
I'm really puzzled as to why this question is being asked. It should have been super clear to MS from feedback a month or two back that lots of developers were deeply unhappy with the way mutable "captures" were the default behaviour for non-record PCs and how any other behaviour had to be achieved via extra code and custom analyzers. We face the situation in my present company whereby the consensus in the development department is that this feature is so far removed from being "fit for purpose", we will be implementing a custom analyzer to block the use of this feature until PCs are "properly" implemented. To put it into context, we already have stories in our backlog to update our code base to C#12 and a "brown bag" session planned to show off the other new features later this month. These are developers that are normally enthusiastic about new releases. This is the first time for any C# feature, in any release, that the team has even remotely contemplated blocking it. Just get on with supporting a means of those PC parameters being "captured" via private readonly fields with the minimum code ceremony possible, as quickly as possible, please. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 31👎 3
-
While I'm not emotional on this, since this is a feedback thread :-), I'm with DavidArno. I think mutable primary constructor parameter is a bad choice. My feeling is that, the designers eye more on the small classes scenario, where primary constructor fits nicely just like records, or senior library developers who absolutely know what they are doing. However, my gut feeling is that majority developers will use it for other scenarios: Half will be for dependency injection, 1/4 for domain objects with single constructor (DTOs use case can be replaced by records). Both are often big classes - there are more average developers than senior ones. And some classes will even span over multiple files by partial. And the code maintainer rotates. Mutation from a far away location is as tricky as mutation made by another thread, especially worse when you are reading an unfamiliar codebase. |
BetaWas this translation helpful?Give feedback.
All reactions
-
i want this: classState(){privatereadonlyTreeNode<State>tree=newTreeNode<State>(this);publicStateSubstate=>tree.Parent.Value;} You see, the main constructor with parameters. The assignment of fields using parameters is already in the execution phase of the constructor. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Because fields are assigned prior to the method body being executed. Primary constructors don't change that. |
BetaWas this translation helpful?Give feedback.
All reactions
-
There is a suggestion in#1026, but is it not being considered very positively? |
BetaWas this translation helpful?Give feedback.
All reactions
-
That proposal hasn't really gone anywhere. I think the issue is that doing so will silently change the order of when certain fields are initialized, which will likely be unintuitive and surprising. It also creates a dependency problem as any member initializers that depend on that late-initialized member will also have to be late-initialized, including in derived types and possibly across assembly boundaries. |
BetaWas this translation helpful?Give feedback.
All reactions
-
How to invoke a method like In general, the question here is how to do something more with a PC. Similar to partial methods, define a construct that gets hooked up in the PC body during auto code gen. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
That's why I said using an intermediate call, say Without special runtime support, the intermediate function would have to resort to reflection. |
BetaWas this translation helpful?Give feedback.
All reactions
-
While I'm sure that's technically possible, I also don't think it would be palatable. There aren't any other features like that in .NET. It also doesn't solve for the addition of of a final initializer, unless the compiler emits such a call for every instance that is initialized. |
BetaWas this translation helpful?Give feedback.
All reactions
-
In Scala, you can just write whatever statement you want directly in the class body. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Unless there's an option to extend this PC with a body, the applicability is limited to the Model and Service types and definitely not to UI modeling such as XAML and RAD interfaces. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'm proposing an approach similar to that ofModule Initializer. Define a Type Initializer attribute and allow the programmer to mark any (private instance) method with this attribute (a method that takes no parameters and returns void) since the whole type has access to the parameters of the PC, there's no need to take any explicit parameters. The automation needs to invoke the method with this attribute in the source-generated constructor. And validate all the rules that apply to the constructor method as if it is the Proxy Constructor (even this abbreviates to the same PC). |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Personally I think we should stay away from adding features to the PC (PrimaryConstructor) parameter list that makes little-to-no sense in any other parameter list.
Creating a public property? Does that make sense to add to any ol' parameter list? I think no. Feels out of scope. That said though; Could the syntax for props-in-PCs translate cleanly to some other tangentially related concept in method signatures? Possibly, but I can't think of any right now. I think that we should not confuse Primary Constructors with "Simplifying data-modelling" because they are tangentially related at best. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 6
-
@AndersKehlet , "if all you have is a hammer, everything looks like a nail" Just about any feature you can think of can be done with generators. Unions? Why bother, just use a generator: [Union]partialclassOption<T>{[UnionCase]privateNone__none;[UnionCase]privateT__some;} And the reason why we don't just use a generator is that the code is pig-ugly, complicated and such non-standard, non-idiomatic code leads to splintering into dev camps. Just because a generator can be used, doesn't mean it should. Generators should be seen as meeting the requirements of domain-specific features, not to bypass proper language design for generic features. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3👎 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I think so, yes. Given the prevalence of captures in C# I think the ability to mark specific parameters and locals as
It already makes sense with primary constructions, given records. The question (IMO) is whether language features are absolutely necessary for this, or if it could be solved in a satisfactory manner via analyzers and source generators. Like records, I do think this is deserving of first class support. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 3
-
Generating public members for PC parameters istrivial and it doesn't make the user code ugly. Generating unions is technically possible for reference types, though I think the shown approach is questionable. Generating unions that are also checked for exhaustiveness is, again, technically possible though in addition to the generator it requires a decidedly non-trivial analyzer. Generating struct unions is... maybe possible? You could have an interface as the common type, but then you can't really guarantee the hierarchy is closed, so you lose exhaustiveness. There'd also be boxing. The language proposal unifies all of this with a consistent syntax that is considerably simpler than what users can do. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@AndersKehlet I agree. :) |
BetaWas this translation helpful?Give feedback.
All reactions
-
@CyrusNajmabadi - So... unions when? 😄 @DavidArno - btw, the reason I think your union example is bad, is that I wrote a union generator two weeks ago and the user code looks like this: [AutoClosed]publicpartialrecordOption<TValue>{partialrecordSome(TValueValue);partialrecordNone;} Needless to say, making the declaration syntax "pretty" is not why I want it as a language feature. Your other concerns are slightly more reasonable, though I think you exaggerate the issue with "non-idiomatic" code. I may have preferences, but I'm not gonna fortify myself in a camp. If I see an option or maybe type in a project, I'll immediately have a pretty good inkling of what it does, and I'll be comfortable using it (even though it'll likely be a mere pseudo-union, an abominable pretender). |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Asking for three features working together: support for always-enforced construction preconditions, support for
publicclassTriangle(intx,inty,intz){primary_ctor_body(){// if for any of the x, y, z the sum of the two other values is shorter or equal,// throw an exception the triangle construction precondition / invariant was violated// because it is not possible to create a valid triangle with these lengths.}}
Now, with 1. and 2. together I can construct animmutable object (due to
|
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
As the property initializers run strictly after the constructor finishes (including derived ones), it seems that you are asking for a post-construction, post-property-initialization method. I think I’ve seen a proposal about such a method, but it was separate from the primary constructors. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Thank you@vladd for the clarification. Bottom line, conceptually, I am hoping for an ability to enforce "object construction invariants". It pains me a bit that currently if I want to do it, I cannot use primary constructors, because they don't have the body of a normal boilerplate-heavy constructor where I would usually put such invariants. It would also be nice to make Overall, looks like the language is adding various nifty features but because they don't work well together, I cannot really use them. Bummer. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Now anything can be initialized anywhere. Feels like C# is moving towards javascript. |
BetaWas this translation helpful?Give feedback.
All reactions
👎 4😕 4
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
It would be nice if the behavior of properties could be defined on the primary constructor parameters themselves as closely as possible to the normal auto-properties themselves. I mean any or all of:
Probably by reusing the relevant property syntaxes. Maybe: publicclassPerson(publicstring Name{get; privateset;}){} I believe that's as far as I would go. If you would need additional possibilities, like inheritance modeling with virtual members, there's the "classic" way to define properties. Also, I believe final initializers would be nice to have.
publicclassPerson(stringname){publicstringName{get;set;}=name;publicoverridestringToString()=>name;// Oooops, this should've been "Name".} As per@CyrusNajmabadi, this seems like a non-issue. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I don't think putting full property declaration in primary constructor is a good idea. |
BetaWas this translation helpful?Give feedback.
All reactions
-
@yugabe That mistake seems hard to make. First, the IDE won't even recommend 'name' at all, once it is captured. Second, if you were to write that, you are immediately told:
So i'm not sure how you're ending up in that state. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
Thanks@CyrusNajmabadi, I didn't know there were analyzers for these. I remember there was talk about this around design, but wasn't solved yet then. Consider my fears alleviated. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
@yugabe You're welcome :) |
BetaWas this translation helpful?Give feedback.
All reactions
-
I wonder if too late to suggest parameter in the PC to be public (instead of private) by default, similar to that of record type. Immutability can be enabled explicitly. |
BetaWas this translation helpful?Give feedback.
All reactions
-
The feature has shipped. It is very intentional that it does not create a public member. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
As I mentionedhere, I'd like to be able to create a field from the constructor parameter, so that it can be accessed with
publicsealedclassExample(fieldIMediator mediator) |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I'd like to see the I'd also like to see accessibility modifiers applied that would promote the parameter to a named member within the type. The two could be combined to promote the parameter to a publicclassFoo(stringfoo,// regular mutable capturereadonlystringbar,// readonly capture,publicstring baz,// public mutable fieldpublicreadonly string qux// public readonly field){// ...} Inline declarations of properties could be interesting also, although I wonder if that's a step too far, especially as I imagine that there's a limit to how complicated a property could be. An auto-property could be palatable, but a partial auto-property or a full property? |
BetaWas this translation helpful?Give feedback.
All reactions
👍 4
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Edits to HaloFour's comment addresses this point, so it's retracted. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I was suggesting two separate features there, that could be combined. Updated example to show some combinations. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
wondering if |
BetaWas this translation helpful?Give feedback.
All reactions
-
Maybe |
BetaWas this translation helpful?Give feedback.
All reactions
-
For those interested by this feature, but want more control over the generated field, I've created a NuGet package which allows you to put |
BetaWas this translation helpful?Give feedback.
All reactions
❤️ 2
-
@CyrusNajmabadi Thanks. I've seen it, but i wanted to give the feedback, since adding dependencies is not always a way that is available in every development environment |
BetaWas this translation helpful?Give feedback.
All reactions
-
Not sure what you mean by adding a dependency :) SGs are something you can just author. They only generate code you would write yourself. Just in a way that is automated so you don't have to manually do it :) |
BetaWas this translation helpful?Give feedback.
All reactions
-
Oh i just meant adding a nuget package which has the source generator as "dependency". obviously it's not really a dependency per se, but a package that is added to the project. I sometimes have customers who are very restrictive with package references |
BetaWas this translation helpful?Give feedback.
All reactions
-
@taori this would be for your own benefit. This would not be shipped to your customers. This is about addressing hte point you made about: Sometimes i have 5-15 DI services for UI classes, and from an intellisense perspective _someService just gets my intellisense into a direction way faster. For your local library/app development you'd use a generator to take care of that drudgery. But you wouldn't be shipping anything new (esp. nuget-wise) to your customers. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Note: This is how Roslyn itself works. We have generators that take care of stuff for us. But we dont' ship the generators to customers. We only ship our normal compiled libraries, which contain the compiledoutputs of the generator. |
BetaWas this translation helpful?Give feedback.
All reactions
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
Can we have a #pragma or something, such that the fields names can get a prefix underscore? for example, Alternatively, we could declare |
BetaWas this translation helpful?Give feedback.
All reactions
-
You could use a source generator that automatically emits code to initialize fields using any name you want. It could be paired with an analyzer that then makes it an error to reference the parameter as a capture outside of the construction lifecycle. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I see C# 12 primary constructors as syntactic sugar to have trivially simple automatically initialized fields. |
BetaWas this translation helpful?Give feedback.
All reactions
-
That is not correct. They are just another way of defining a constructor with parameters. Just with parameters that can be used by the members nested within the type/cosntructor. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1
-
It's trivial to just use that (and have other developers use it) once written once. |
BetaWas this translation helpful?Give feedback.
All reactions
-
Just want to add some positive comments among all complaints. I like the feature. It reduces the ceremony for DI, and the lack of readonly isn't too big of a deal. Just don't mutate the value, and you'll be fine. I don't know if the |
BetaWas this translation helpful?Give feedback.
All reactions
-
Ah, the old "you don't need a seat belt. Just don't crash your car, and you'll be fine" line of reasoning 😂 |
BetaWas this translation helpful?Give feedback.
All reactions
👍 1👎 1
-
Given such a small incidence of this actually happening, I'd say that is fairly accurate. You don't need every safety measure. Not if it's not really that necessary or valuable. |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
-
@DavidArno I do use a seat belt, but I do not use a helmet when driving. I assume you do. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 5
-
I would like to be able to affect the generated field name, i.e. to allow underscore prefixes like requested here:#7565 I know that primary constructor parameters are technically parameters andmight not be kept as fields/properties but they are in the current implementation, I even found it mentioned on the MS page:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/instance-constructors#primary-constructors
I also find it confusing that primary constructors work differently for records than for (typical) classes, i.e. I cannot apply |
BetaWas this translation helpful?Give feedback.
All reactions
😕 1
-
@baterja the exact purpose of using records is precisely to get members and proper behavior around those Members. When using a non-record type we don't want that since that's what records are for in the first place. |
BetaWas this translation helpful?Give feedback.
All reactions
-
I know, but only because I've been using C# for over 10 years and was up to date when those changes were introduced. Newcomers think primary constructors will behave alike in records and not-records, and also because |
BetaWas this translation helpful?Give feedback.
All reactions
-
TypeScript primary constructor syntax involves providing an accessibility modifier. The behavior of primary constructors in C# is identical to that in Scala and Kotlin, and it's not considered confusing in either of those languages. It's expected that if you want the class to behave as a data carrier and define the schema of that data based on the constructor that you need to opt-in to that by making the class an equivalent of a record, via |
BetaWas this translation helpful?Give feedback.
All reactions
-
This feature won't be useful for me until primary constructors can have bodies for logic (like the "do" block in F#). I am mostly stuck with normal constructors for now, but if primary constructors eventually get logic bodies, I would probably be able to replace almost all of my normal constructors with primary constructors. The syntax for primary constructors is just must nicer overall. |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
-
USE CASE
Two issues: (1) No body supplied for the primary constructor!!!! What genius thought this was a good idea!!! In general, this whole attempt to shoehorn a “primary constructor” into the language has not been thought through and is incomplete at best. I my opinion, new isn’t always better. In this case introducing a half-baked idea all for the sake of brevity is just plain stupid. |
BetaWas this translation helpful?Give feedback.
All reactions
👎 1
-
Why do you use the |
BetaWas this translation helpful?Give feedback.
All reactions
👍 2
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
-
I propose the following solution for enhancing functionality within the context of PC: For backward compatibility, the constructor method should retain the same name as its containing type. Historically, nearly all access modifiers have been employed to define constructors, except for the newly introduced Thus, this new modifier can be used to define the additional method that gets invoked as part of the PC, with the invocation order determined using an attribute. publicpartialclassMainPage(MainViewModelviewModel):ContentPage{// Newly proposed method that acts like a constructor body, gets invoked in the PCfileMainPage(){InitializeComponent();// UI InitializationBindingContext=viewModel;}} |
BetaWas this translation helpful?Give feedback.