Preview Feedback: C# 12 Primary constructors #7109
Replies: 45 comments 345 replies
-
I kinda wish the blurb under "What's New" was a little more fleshed out, particularly as it describes the interplay with primary constructors with "record" and which feature contributes what behavior. The documentation linked under "Instance constructors" is much more explicit and hopefully will be enough to dispel the confusion that has already led to comments in the discussions regarding member synthesis. |
Beta Was this translation helpful? Give feedback.
-
Do you mean in docs? I really rushed @BillWagner to ensure we had a link. There will be more, and as usual Bill will integrate the new feature with our broader docs, so a year from now you don't have to sift through "what's new" for details. |
Beta Was this translation helpful? Give feedback.
-
If I'm understanding this correctly, the new primary constructor syntax forces using the same name for both fields and parameters. This seems like an awkward limitation, because naming guidelines for fields and parameters are usually different (e.g. for dotnet codebase itself). I don't really see myself adopting this feature if it forces inconsistent naming. That would just make the code harder to read. |
Beta Was this translation helpful? Give feedback.
-
The document miss use cases when do you want to use this feature, something like before and after, not the conversion of the code per se, more to what we are trying to tackle. |
Beta Was this translation helpful? Give feedback.
-
I think the primary constructors miss most of the pain points developers have when writing constructors. Consider a common public class Service1
{
private Service2 Service2 { get; }
private Service3 Service3 { get; }
private Service4 Service4 { get; }
public Service1(Service2 service2, Service3 service3, Service4 service4)
{
Service2 = service2;
Service3 = service3;
Service4 = service4;
}
} Boilerplate on boilerplate on boilerplate. I typed Also very common. The current primary constructors don't really solve this problem, almost at all. Additionally, introducing class-level closures is not at all beginner-friendly and not even intuitive at all for us oldies. Consider TypeScript, on the other hand: class Service1 {
constructor(private service2: Service2, private service3: Service3, private service4: Service4) {
}
} Simply putting the visibility modifier in front of parameters make them properties. Transpiled to JavaScript, this will actually set the fields (no need to "define" them in JavaScript) first thing in the constructor body. There's no need, nor do I see any benefit for the primary constructors to behave as closures. I won't define a parameter I don't want to use in the class. I want to, almost always, set a property on the class, just as records do. It would be a lot better if C#'s implementation was more similar to TypeScript's. I could also imagine the primary constructors to take auto-property declarations or similar. This would allow for get-only or get-set properties to be defined. Or just make them readonly fields and be done with it. I think either allowing more customization or intentionally limiting the scope should be a focus. This is not a good feature in its current state in my opinion. It adds more cognitive load to see if there are any properties/fields/primary ctor parameters are there. The goal should be to make defining classes easier, so I don't think full customization should be available. |
Beta Was this translation helpful? Give feedback.
-
Lots of good discussion here, and I'm a tad rusty... but seeing this example above really excites me:
I'm always a huge fan of reducing boilerplate and allowing developers to express themselves succinctly and powerfully. This could be really powerful for standard IoC services. |
Beta Was this translation helpful? Give feedback.
-
Please correct me if I'm wrong but I'm not seeing how primary ctor body is used. E.g. ctor-time parameter validation, non-static field initialization, etc. This is one of the things I'm really missing in records too. In F#, for example, your type's body is a primary ctor body basically. This makes initialization in primary ctor very convenient. |
Beta Was this translation helpful? Give feedback.
-
Based on some initial testing in SharpLab my major concern is mutability. I understand that parameters are always mutable and this would be an exception to that rule, but in this scenario the scope is so much wider: for a normal parameter it is the method body, in this case it is the whole class. That can quickly lead to, not that obvious, state change (and confusion with a local variable one might have forgotten to declare).
Personally, I would prefer immutable by default (yet I would also prefer property instead of field by default), but I get that would be a deviation from the general rule as well. I would also suggest naming to be One last comment to the reduction of boilerplate with DI: I only write |
Beta Was this translation helpful? Give feedback.
-
Suppose, we need to add some validation for primary constructor parameters, e.g. check that it is not null or empty: public class NamedItem(string name)
{
public string Name => name;
} To do this, we have to go back to the old syntax (5 lines added): public class NamedItem
{
public string Name { get; }
public NamedItem(string name) // <-- ctor parameters moved
{
ArgumentException.ThrowIfNullOrEmpty(name);
Name = name; // <-- assignment moved because the scope of the name parameter has changed
}
} This can be done automatically by the IDE, but writing a primary constructor and then converting it to a simple constructor at some point is not nice. This is also a pain for Kotlin solves this with an Example with public class NamedItem(string name)
{
init() => ArgumentException.ThrowIfNullOrEmpty(name);
public string Name => name;
} |
Beta Was this translation helpful? Give feedback.
-
(As suggested by Kathleen, this is a copy of the comment I left on the announcement blog. I apologize for the verbosity, it was originally meant as a chatty blog comment conveying a first impression, not as well-researched, targeted technical feedback.) First, I’d like to thank you for continuing to improve C#. This continuous improvement is the main reason we switched from VB.NET to C# some time ago. (Nullable reference types and record types are awesome, and I’m already looking forward to all language features that help reduce INotifyPropertyChanged boilerplate.) That having been said, I share the skepticism around primary constructors for non-record types. I’ll try to explain why:
There’s one caveat to my criticism: I don’t (or rather: very seldom) use “constructor dependency injection”. Some have speculated here in the comments [note: meaning the blog comments] that this is the intended use case for this feature. If this is the case and it solves a real problem: That’s great, please ignore my comment, proceed, and don’t let me stop you. If it isn’t: What use case did you have in mind for this feature? Which common problem is it supposed to solve that justifies the cost? Addendum: Point 3 (the accessibility of the primary constructor) might be a bigger issue than I originally thought: Jared wrote in a comment further up that you want to "get the fundamental feature out now, get feedback, and evolve again in the next release". The thing is: You won't be able to change the default accessibility of the primary constructor without a breaking change to the language. |
Beta Was this translation helpful? Give feedback.
-
Just throwing my own two-cents onto the primary constructor pile: I usually can't use the primary constructor on records (or now classes) because I also have to define a parameterless private constructor for DataContract de-serialization purposes. Might it be possible to augment types that use the primary constructor to automatically generate a parameterlesss private constructor either when a record is decorated with a Thanks! |
Beta Was this translation helpful? Give feedback.
-
I think this feature works well in a lot of cases. I would like to see two improvements. The first would be allowing to add a method body for the primary constructor. A second improvement would be to allow the Primary constructor parameters to be "in" parameters. public class ServiceA(in Service1 service1, in Service2 service2) //Allow the "in" modifier. Make the backing fields read only.
{
PrimaryConstructor{
//Allow the class to do any additional logic with a primary constructor method body
}
} |
Beta Was this translation helpful? Give feedback.
-
We need internal class CreateCustomerCommandHandler
(ICustomerRepository customerRepository, IMapper mapper, IUnitOfWork unitOfWork)
: IRequestHandler<CreateCustomerCommand, ErrorOr<CreateCustomerDto>>
{
} |
Beta Was this translation helpful? Give feedback.
-
@KathleenDollard asked me to post it here: One more thing which concerns me with PC. I’d propose having an opportunity to distinguish these two cases: properties vs private/protected(?) readonly (?) fields. Readonly/init properties:
Declared as readonly private field.
OR
Declared as protected readonly field
OR
The versions with explicit modifiers are verbose. I guess readonly is desired in 99% cases, so, I’d allow yet another contextual keyword
Visibility modifying prefixes
is equivalent to
P.S. |
Beta Was this translation helpful? Give feedback.
-
It's interesting to duplicate this functionality from record to class, I do sometimes used records exclusively for this, which is not ideal. But please do not implement it differently. This will add obvious pitfalls if we have auto implemented props in one case but not in the other, and hidden backing field in a case and not in the other. You are explicitly doing this functionality as a replica of the record's one, so yes, if there are differences, in a way you are explicitly hurting us. I'm down voting this one as I'm sure it will make my usage of C# harder and I'll have to fix bugs caused by this confusing implementation. I'm even convinced that my misusage of records is better than this.
@HaloFour Developers are impacted by implementations. Lambda is a special beast with its capture problems, I have learnt the hard way that I better not ignore them, and it's the same here. |
Beta Was this translation helpful? Give feedback.
-
I didn't expect my idea to have such a big response. I just want to express my thoughts and hope that these ideas can bring another kind of inspiration to everyone.
So what should we do now? Can deleting this post now solve the problem
…------------------ 原始邮件 ------------------
发件人: "dotnet/csharplang" ***@***.***>;
发送时间: 2023年5月25日(星期四) 晚上8:50
***@***.***>;
抄送: "windows ***@***.******@***.***>;
主题: Re: [dotnet/csharplang] Preview Feedback: C# 12 Primary constructors (Discussion #7109)
The majority of the had been considered by the team during the design of the feature.
If all of this had been considered beforehand, why invite feedback? The decisions are made, so don't give the users the false impression that the team cares what those users think.
While I understand that other people have the same concerns, bringing it up without additional information doesn't really change the equation.
There are literally hundreds of comments here with reams of additional information. Non sequitur.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Sorry, I would like to express a profound apology first. I did not expect such serious consequences to occur, but I just wanted to express some ideas and hope that these ideas can bring another kind of inspiration to everyone. I didn't expect the consequences to be so serious, and I am very sorry
…------------------ 原始邮件 ------------------
发件人: "dotnet/csharplang" ***@***.***>;
发送时间: 2023年5月25日(星期四) 晚上9:21
***@***.***>;
抄送: "windows ***@***.******@***.***>;
主题: Re: [dotnet/csharplang] Preview Feedback: C# 12 Primary constructors (Discussion #7109)
@DavidArno
If all of this had been considered beforehand, why invite feedback? The decisions are made, so don't give the users the false impression that the team cares what those users think.
More feedback. There's always things that may not have been considered or puts a different angle on things. Numerous proposals have been amended at this stage due to community feedback, if not outright canceled. The responses to the feedback isn't to dismiss it, it's to explain why the team reached the design that they did. But this is late-stage feedback at this point so the proposal does have quite a bit of inertia behind it.
There are literally hundreds of comments here with reams of additional information. Non sequitur.
Repetition alone is not new information.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
This comment has been minimized.
This comment has been minimized.
-
`var emp_primary = new Employee("冯", "先生", "56820168@qq.com", 75000) by ~; It might be more appropriate to change it to something like this var emp_primary = by ~ new Employee("冯", "先生", "56820168@qq.com", 75000); Recently, I am busy with work and rarely log in GitHub I think the best thing to do is to remove the primary constructor, because it doesn't seem to help much beyond simplifying attribute assignment |
Beta Was this translation helpful? Give feedback.
-
I wish that the primary constructor syntax wasn't identical to the record syntax, for two reasons: Identical syntax of record, but it's a class and it actually works in an a very different way
The class above will compile with only a warning, saying But in a class, a primary constructor with no body does not actually do anything at run-time. It does not generate auto implemented public properties like in a record. So why would I want a constructor which does nothing? A primary constructor is still a constructor. Why does the primary constructor syntax allows me now to write constructors without a body? It seems to me like with a class primary constructor with no body, I'm now able to write something meaningless like:
Is there a reason why properties are not automatically implemented for class too? Not only it will be on par with record, but it will also make body-less primary constructor for classes make more sense. The primary constructor is a great new feature and I can see scenarios where it will help a lot with making the code more clear and explicit. Dreaming of record-like auto implemented properties in classAnother reason is that I so wished I could someday be able to have class implemented with the great succinctness of record:
Similar to a record, the above code would make the class have an automatically generated public property Why not just use a record instead?A record can do a lot more than it's needed sometimes. For example, everyone recommends using records for DTOs (Data Transfer Objects). But why would I need equality for example in DTOs? |
Beta Was this translation helpful? Give feedback.
-
Not 100% sure this applies to primary constructors or is a more general thing, but is there any thoughts on allowing capture of I.e., something like the following: public class ClassA(int x)
{
private ClassB InstanceB { get; } = new(this, x); // Currently invalid as `this` is not available in current context.
// ...
}
public class ClassB(ClassA instance, int x)
{
private int X { get; } = x;
private ClassA InstanceA { get; } = instance;
// ...
} From what I can see, if I want to use My understanding is that I'd instead have to use an regular constructor in the following manner: public class ClassA // Cannot declare primary constructor as it would conflict with regular constructor.
{
public ClassX(int x) // Same parameters as previous primary constructor from first example.
{
InstanceB = new(this, x);
}
private ClassB InstanceB { get; init; }
// ...
}
public class ClassB(ClassA instance, int x)
{
private int X { get; } = x;
private ClassA InstanceA { get; } = instance;
// ...
} |
Beta Was this translation helpful? Give feedback.
-
After playing with this new feature, I found a place that could potentially get some improvement. When I mix primary constructor with var test = new Test("John Doe"); // [CS9035] Required member 'Test.Name' must be set in the object initializer or attribute constructor.
public class Test(string name)
{
public required string Name { get; init; } = name;
} I understand that since |
Beta Was this translation helpful? Give feedback.
-
I really don't like this feature. I don't like it for records either. I think there should be precisely one way to declare a property in your class otherwise it just becomes confusing. You need to check for proper properties, and then you also now need to check for a primary constructor. For the sake of tersity, that's just not worth it imho. |
Beta Was this translation helpful? Give feedback.
-
Happy to see this absorbed from F#, if I have to create a new constructor I have to call the primary constructor in the end to create that object, though I wish F# to just let me have functions of the same name to be automatically converted to one single function with multiple overloads I doubt I should talk about that language in C# discussion |
Beta Was this translation helpful? Give feedback.
-
Can someone tell us how we should find the primary constructor with reflection? Will we also get a |
Beta Was this translation helpful? Give feedback.
-
I updated my code to C# 12 and primary constructors, and I found what I think is a bug: I get a CS9110 about the use of a constructor parameter with ref-like inside an instance method, but the method is itself a member of a ref struct. The error goes away when I uncomment line 3, which makes me believe this is a bug. internal ref struct CharSpanSplitter(ReadOnlySpan<char> value, char separator)
{
// private ReadOnlySpan<char> value = value;
public bool MoveNext(out ReadOnlySpan<char> result)
{
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
if (value == ReadOnlySpan<char>.Empty)
{
result = ReadOnlySpan<char>.Empty;
return false;
}
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
var index = value.IndexOf(separator);
if (index == -1)
{
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
result = value;
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
value = ReadOnlySpan<char>.Empty;
return true;
}
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
result = value[..index];
// CS9110: Cannot use primary constructor parameter 'value' that has ref-like type inside an instance member
value = value[(index + 1)..];
return true;
}
} |
Beta Was this translation helpful? Give feedback.
-
Not sure if this the correct place to ask/provide this, but is there a mechanism for doing null checking on parameters to a primary constructor? In my DI setups, I like to ensure the properties are in fact set. Any way we can utilize [NotNull] or some other attribute to do that internally? |
Beta Was this translation helpful? Give feedback.
-
we are using primary constructors for records and classes for a while now and we don't want to miss it. the only issue that we are having, is that it is messing up with the naming guidelines. for example we have the guideline, that private fields should be prefixed with I would love to have attributes to influence how the arguments are used: [Flags]
public enum NamingRules
{
PascalCase,
UnderscorePrefixed,
// others
}
[PrimaryConstructorNaming(PropertyNameRules = NamingRules.PascalCase, FieldNamingRules = NamingRules.CamelCase | NamingRules.UnderscroePrefixed)]
public class MyDTO([Property] foo, [Field] bar)
{
public void FooBar()
{
Console.WriteLine($"{Foo}.{_bar}");
}
} of course this needs to be well designed, but this is our experience if PrimaryConstructors I hope this feedback helps. |
Beta Was this translation helpful? Give feedback.
-
It's odd to leave PC w/o any body, for this reason numerous times we had to revert to using regular constructors in the past. |
Beta Was this translation helpful? Give feedback.
-
Hi, In Kotlin language, it has similar feature: class SomeClass(val x: Int, val y: Int) {
} This might equivent with C#'s: class SomeClass(int x, int y) {
} In Kotlin, I can get primary constructor with this via Reflection: |
Beta Was this translation helpful? Give feedback.
-
Primary Constructors for non-record classes and structs are included starting in C# 12 in preview 3. You can use this discussion for feedback as you use the feature.
You can learn more about primary constructors in non-record classes and structs at the What's new in C# 12 article with links to all new content for primary constructors.
Beta Was this translation helpful? Give feedback.
All reactions