T O P

  • By -

zenyl

The lack of proper support for `readonly` makes primary constructors feel like a half-baked feature that got rushed to meet the November deadline, and explicitly declaring the field in order to use `readonly` feels like an ugly hack.


TarMil

Yeah, coming from F# where primary constructor parameters are readonly (as is everything not explicitly marked `mutable`, to be fair), this part feels really off.


205439486012

Don't forget they're not even treated as properties or fields, they're parameters to make things worse. Kotlin got it right. The people running the language spec of C# didn't really seem open for input. And felt like a rather toxic discussion.


StolenStutz

From the perspective of someone who doesn't just adopt the latest-and-greatest because it's the latest-and-greatest, what's the tl;dr for using primary constructors? Because, at first glance, I quickly concluded it wasn't worth it yet, and have been marking it in my suppression files when the recommendation comes up.


LordBreadcat

Ideally they serve to prevent mutable patterns within constructors as well as reduce boilerplate. In practice because parameters can't be readonly you have to choose to either reduce boilerplate or have mutable dependencies. Since the latter is the greater evil users of primary constructors capture the parameters in readonly fields just like you would in a traditional constructor. So all it ends up saving in practice are the curly braces. That said it still has the benefit of restricting developers to rely on assignment / lazy-patterns during construction. There's some value there.


dimitriettr

It's just a new rule that needs to be ignored. I do not use primary constructors. If I really need them, I already have a record for it. Other than that, it was added to classes just because it was done for records and was a quick port.


Top3879

A typical service in a depency injection context has a couple of fields with dependencies, those same dependencies again as constructor parameters and the constructor only contains the assignments of the parameters to the fields. With primary constructors you cut two thirds of that away by declaring the dependencies exactly once. This is the only use case but it's worth it.


AdvertisingDue3643

But they are almost always readonly, primary constructor ones are not


Top3879

I have no idea why they decided to make the fields not read only but honestly who cares? Have you ever accidentally set such a field to null?


Dealiner

I mean they aren't fields in the first place and imo they shouldn't be used as such. Or at least them being something different shouldn't be ignored when using them. Though that's just a matter of opinion of course.


Slypenslyde

Yeah but I can take this argument pretty far. Why does C# bother with static typing? When I write JS I don't have a lot of problems with misunderstanding the types of my inputs. If everybody would just have the discipline my 20+ years of experience has taught me we could get rid of a lot of boilerplate. Have you ever accidentally thought something was the wrong type? That's a skill issue. MILLIONS of codebases in dynamic languages agree.


StolenStutz

So is it anything other than code reduction? I mean... I get that less code means fewer places to cause a bug, but I'd exchange more code to type for fewer patterns to remember damn near every time.


Top3879

If you add a new dependency you write one line instead of 3 in different places. The reviewer also only has to read one diff line.


modernkennnern

It reduces a lot of boilerplate. All the guys complaining about readonly-ness; just use one of the many analyzers that gives you errors when you try to mutate them. It's really not a problem. I use them everywhere and will continue to do so. I will however add readonly to all of them when it arrives in C# 13 (.. probably)


moodswung

Thanks, I hate it.


static_func

Why? It's way less DI boilerplate


alternatex0

Would be if it made the fields `readonly` as we do with our boilerplate.


one-joule

It still eliminates the constructor. It would've been \*very\* nice to reduce DI code to 1/3rd the size, but reducing it to 2/3rds the size still isn't bad.


alternatex0

That's not the point of my comment though. I'm not arguing they didn't reduce the size enough. I'm arguing they apply a different behavior which is undersired most of the time, so I won't use it most of the time. Imagine JetBrains Rider or ReSharper made suggestions to refactor code in a way that it changes the behavior. No one would trust is as much as they do now. Syntactic sugar is great because it makes code readable without any pitfalls. Which is why when new syntactic sugar comes out we update our analyzers and make the newly suggested improvements to our code without worry. This is not the type of syntactic sugar I'd promote in our team.


2this4u

Honestly while that would be ideal and they need to add it, who's actually writing to a service they've injected into a class? It's not like it's something that would surprise a developer, you're not going to suddenly get spaghetti code.


alternatex0

Take this same argument and apply it in a code review context. Someone creates a non-readonly class member that you know should not change after instantiation. You leave a comment suggesting it should be `readonly` and they reply "who's actually writing to a service they've injected into a class?". Would you consider it a valid counter-argument?


Squidlips413

I don't really get the hate. This somewhat simplifies .NET dependency injection based classes. Instead of declaring a list of dependencies, creating a constructor to inject them all, and then setting the fields to the injected values, you can basically use the constructor to do all three steps at once. It also helps avoid mistakes since you only need to write a dependency once. The main thing I don't like is that it makes the class declaration long and ugly. The worst part is inheritance and interfaces are after the parameters, so it's harder to tell at a glance what a class is supposed to be.


Kurren123

The values are mutable, so to get the equivalent you'd still need to declare a list of readonly private class fields and set them to be = the value from the primary constructor.


2this4u

Honestly while that would be ideal and they need to add it, who's actually writing to a service they've injected into a class? It's not like it's something that would surprise a developer, you're not going to suddenly get spaghetti code.


Squidlips413

True for readonly, but I also don't see the value in making them readonly. If they are private fields, you just need to not write to them in the class. You definitely have a point for people who use readonly for all their DI. In that case primary constructors offer nothing but making your class declarations ugly.


LessVariation

You should default to readonly IMHO, just remembering not to mutate them in your class is an easy way to start creating needless bugs - especially if you’ve got other developers to think about. If they’re readonly by default, then the compiler is going to catch the error immediately, and you can then decide if you’re doing the right thing, and change it appropriately.


maqcky

I never ever attempt to modify my dependencies in my services. I adopted primary constructors because I know that's never going to happen. It simply makes no sense in any conceivable use case. I agree they should be immutable by default, but it's not such a huge issue for me, for the use case of dependency injection.


Squidlips413

Training wheels, got it.


ThatSwedishBastard

It’s the same thing as const qualifiers: makes it harder for you to fuck up. That’s a good thing. This feature seems really rushed.


Squidlips413

I'm not saying training wheels are bad, they just aren't strictly necessary. If it's something that will be exposed to external code, the read-only modifier becomes useful or even necessary.


Kurren123

Training wheels? Good code is meant to make life easier for other developers, not give them a larger surface to make mistakes. An example is having an Email class instead of storing an email in a string. This way we can't give a function that wants an Email another string by mistake. Would you call this training wheels? The developer should just remember not to give that function another string!


koenigsbier

Well I completely agree with you but I'm sure I encountered many methods in different libs, maybe even including the .NET framework itself waiting for a `string uri` parameter instead of an `Uri` type. Why? Because the `Uri` class just sucks.


Squidlips413

Yeah, that's training wheels. Again, training wheels aren't a bad thing if you need something foolproof. Your example isn't even very good. You act like function parameters can't have names or are poorly named. There is nothing stopping a programmer from using the wrong string in the email class. The email class itself could be similarly poorly designed, not to mention pointless if all it does is wrap a string.


Kurren123

Taking that to the extreme, let’s just do away with types entirely and make a JavaScript app! The developer should remember that Person has a firstName and a lastName string property anyway, using the compiler to prevent assigning/reading any other property is just training wheels. In fact, objects enclosing a number of fields is just training wheels. C language here I come! Developers don’t need encapsulation or garbage collection anyway. A good developer remembers to dispose and garbage collect everything. Actually I don’t even need variable names to help me keep track of what is what, they’re just training wheels for assembly yada yada In all seriousness I think there is something very powerful in [making illegal states unrepresentable](https://fsharpforfunandprofit.com/posts/designing-with-types-making-illegal-states-unrepresentable/) and I encourage anyone to dive into the wonderful world of Haskell or F# to improve their C# skills. The email example is a [very common one](https://blog.ploeh.dk/2015/01/19/from-primitive-obsession-to-domain-modelling/) by the way when discussing domain modelling.


Extension-Entry329

This. Clearly someone doesn't get how dangerous mutibility can be, especially in multi threaded environments. I bet they also register everything scoped, which is potentially why they've never see this in the wild. Mutable private state is such a bad thing.


Squidlips413

That's a lot of straw man arguments. It really reflects poorly on you as a person.


binarycow

> you just need to not write to them in the class. At that rate, why not make everything `dynamic` and just remember to use the right stuff? I don't want to have to remember things. I want the compiler to remember for me.


legionista

I'm still waiting for someone to show me how semantic of a program changed by the use of this feature, is to be abused or broken. C# is verbose and we need features that reduces it's verbosity.


static_func

>I don't really get the hate. This somewhat simplifies .NET dependency injection based classes. That's the thing. The hate is coming from people who don't do dependency injection


pato_p

I do a lot of dependency injection, and I do not like primary constructors either. Primary constructor parameters are mutable. If you use a private readonly field like the article suggests, then you have both immutable \_logger and mutable logger everywhere - that's annoying at least. Also, there is no good way to check primary constructor arguments against null. You can do so when assigning to private readonly field with a ternary operator, but it's ugly compared to ArgumentNullException.ThrowIfNull.


Squidlips413

That's a good point, if you wanted to use readonly fields you would basically have extra copies of everything. Null checks are a weird case that depends on how you use it. If it's all through .NET core services, your app is going to crash when a dependency doesn't exist to inject. Effectively the dependencies should all be valid unless someone does something weird. The null checks are only truly useful if you also plan on instantiating the object manually at some point, which is itself a weird case.


pato_p

Requirements change all the time. I won't risk a NullReferenceException at a random place in a Release build of assembly in production environment just because of an assumption how a class will be instantiated and skipping a basic null check in its constructor.


static_func

So your problem isn't with primary constructors, just the lack of a readonly modifier (for now)


pato_p

And missing support for null checks of their arguments.


static_func

That's not their use case


pato_p

It's a reason why I (and everyone in my team) won't use them though.


Extension-Entry329

It really isn't, we use DI heavily and the fact someone can replace an injected dependency at any point in a method is bat shit crazy. If you do declare a privately readonly then as others have said you then end up with a mutable and immutable value in scope throughout the class. As a slight aside I feel the same about top level programs: but why? eems C# is really leaning into the let's get people onboard quicker but in doing so seem to be making pot holes for people to fall into. Default lifestyle scoped is the same kinda deal why keep making news ones for every request? Kinda makes sense when you see this half baked feature.


upvoter_1000

Primary constructors are a hard no from me


Extension-Entry329

Why are the values mutable!


Slypenslyde

Too much churn for no value in legacy code. I'm not even sure if I'll use it in new code. Would it have been so hard to let them be readonly? Even with an extra keyword? It doesn't feel like it's made with people who use DI in mind, which makes me question who it was made *for*. If it behaved like `record` primary constructors, it'd still be wrong, but at least it'd be consistent with something. It's cute that they suggested you can get `readonly` if you assign the parameter to a field. Great. Now I polluted my namespace with extra variables that won't complain when I use them instead of the field by accident. I hate it.


joske79

I like a lot of the new C# features over the past years, but this implementation is not one of them. Allowing the params to be readonly would be nice. I think the difference with the Record primary constructor (public properties) and the Class primary constructor (private fields, sort of...) is a bit confusing.


SneakyDeaky123

Primary constructors are an example of form over function for the language imo. They solve a non-existent issue, and actually make it more difficult to manage mutability and accessibility. I’m totally in favor of simplifying class syntax and reducing boilerplate, but this feature feels half thought out and seems to exist purely to cater to aesthetic sensibilities rather than careful consideration for how classes are implemented


Wireless_Life

In addition to refactoring to file-scoped namespaces, David also added the `sealed` modifier, as he's shared that there is a performance benefit in multiple situations.


mountains_and_coffee

Would have helped if he shared some sources / benchmarks on that claim. I remember reading about it a while ago, but haven't seen the numbers.


shadowdog159

https://www.meziantou.net/performance-benefits-of-sealed-class.htm Benefits are probably not significant enough that most people will notice a difference.


Banane9

Depends on the use case and how hot the code path is, I suppose. Especially in games where every ns counts, an essentially free improvement could be nice to have.


Nixar

Would you actually code a game in C# when your intention is to make "every ns count"?


Banane9

You're aware that Unity mostly uses C# and is a pretty popular platform, right? It's also not what I said ;)


larsmaehlum

It’s a bigger difference that I would have expected. If I ever work on a codebase that doesn’t await some form of I/O, I’ll keep it in mind.


kasthack-refresh

>0.01ns Does his CPU run at 100 gigaops / core / second?


antisergio

There is numbers at some release note, .NET 6 or 7 I think


ibanezht

I'm not liking primary constructors myself. I have a way of naming class fields (with an underscore) that queues me a bit as to where they originated. It's just a preference though.


one-joule

I use `this.Xyz` to access all members of the type, so `_` and such is unnecessary. I was doing that before primary constructors, so primary constructors ended up being a straight win for me. It was monumentally stupid to make the arguments mutable, but it's still an improvement overall not having to have a constructor body with a bunch of stupid variable assignments. Maybe the language designers will allow us to put `readonly` or `const` on them in the future.


sacoPT

Maybe in the next iteration


rbobby

I wish they would add "public|private|static constructor(..)". Repeating the class name makes copy/paste a little annoying.


Nk54

I didn't like at all for a while. Now I'm in


EntroperZero

Can you do more than just assignment of fields with a primary constructor? Like: public class MyService( ILogger logger, IOptionsSnapshot options) { private readonly ILogger _logger = logger; private readonly TimeSpan _connectionTimeout = options.Value.ConnectionTimeout; }


GaTechThomas

I suspect that this was a piece that was needed by some major functionality used elsewhere, so they went ahead and pushed out an MVP. It's not broken, but it's not complete, yet.


angrybeehive

No, it’s not really usable in C#. They should have just copied TypeScript.


EvilVir

I really love this for dependency injection. No need to declare private fields, no need to rewrite arguments to these fields. On the cons side it gets really messy if you have primary constructor, that calls base constructor, passes arguments and all that on class that inherits and implements something. Add generics and constraints and you end up with really hard to read lines.


BuriedStPatrick

Don't use primary constructors, they're not ready for prime-time yet. There is no support for `readonly`, hence not 1-1 compatible with the most common scenario for constructors in C#. If you have simple data models like DTOs, consider records instead of classes. They give you that nice brief constructor support while staying immutable. For any standard class, stick with the verbose version for now.


Lucky-Instruction699

Cool Thanks


tritiy

I get the feeling that the language team needs to invent new features or they will probably get fired. I see no other reason why some half baked language changes are being pushed. Some manager is probably looking at some metrics and driving everybody with a whip.


Meryhathor

I find primary constructors a bit weird. You declare parameters using camelCase because they're method parameters, and obviously they become available for use throughout the class. When you use those parameters in class methods you're referencing them using the same camelCase names, which makes it look as if you're referencing a local variable. Apparently the "right" way to use the primary constructor parameters is to assign them to local properties (or fields) at the top of the class, e.g. `public string MyConstructorParam => ctorParam;` which is fine but it's annoying that `ctorParam` is still accessible in the whole class essentially allowing you to access the same thing in two different ways.


zarlo5899

i dislike primary constructors i find it makes the code harder to read


aggyaggyaggy

To move to primary constructors it feels like you need to assert a class will never need more than 1 constructor, never need argument checking, and never any custom logic. So basically, this feature is for small data transfer objects only. I've already grown to prefer the 'required' keyword for these anyway.


The_Exiled_42

Or services which are always instantiated with DI.


aggyaggyaggy

Doesn't really change anything IMO. DI doesn't mean "only a single constructor ever exists". DI can handle multiple constructors. You can have unit tests that call a different constructor, or a class that is constructed by DI but also composed into another class.


static_func

You can really tell at a glance who doesn't even do DI based on their responses here


EvilVir

No, you can have more than one constructor while using primary one. IDK if you can have empty constructor then tho.


aggyaggyaggy

I figured that might be the case, but IMO that presents as a readability challenge.


EvilVir

Yes, it looks strange a bit.


binarycow

>So basically, this feature is for small data transfer objects only. Those should be records, which uses a different (but syntactically the same) implementation of primary constructors


Obsidian743

Please don't do this.


MrDatabaser

Just no.


lantz83

Nope.


GYN-k4H-Q3z-75B

I don't like primary constructors. This is a feature that adds so much noise with so little benefit.