BrandGhost

NOBODY Can Agree On Primary Constructors in C# 12!

Another great debate in dotnet emerged with primary constructors in C# 12! A new feature as part of the language version, dotnet 8 and C# 12 gives us primary constructors on classes and not just records. Why is it that nobody can seem to agree on their usage? Let's dive into the newest great debate! Have you subscribed to my weekly newsletter yet? A 5-minute read every weekend, right to your inbox, so you can start your weekend learning off strong: https://subscribe.devleader.ca Check out all...
View Transcript
and now we're going to get into the part that people can't seem to agree about in this video we're going to be looking at primary Constructors we're going to start by looking at the record type that was introduced and how record types have primary Constructors and then more recently in C2 we got primary Constructors on classes so I'll be comparing and contrasting those and giving my opinion on where people can't seem to agree on the usage of primary Constructors a quick reminder to check the pin comment for a link to my free Weekly Newsletter and let's go jump right over to the code all right here in visuals Studio we're going to be looking at a record a classic Constructor for a class as well as a primary Constructor for a class so we're going to start with the record type and I'm going to scroll down a little bit more to pull it onto screen here but here we can see a record declared with a primary Constructor and what this allows us to have is parameters that are passed in so an integer that's an ID and a string that's a name and these become properties that are effectively read only and this is a super nice shorthand because this is what you used to have to write and now that I've added that whole thing on screen you can see this used to be the way that we would have to declare things like data transfer objects or dto records are a great fit for a dto but look at how verbose this used to be we used to have to write so much just to be able to have an ID field that's an integer and a name that's a string we used to have to declare these arguments to be passed in then we would do an assignment right here in the Constructor but we would also have to declare these properties so that would mean that for every type of property that you want to be able to add you need to go touch three different spots one here on line 50 we'd have to go add another line between line 53 and 54 and then add a new line after line 57 three different spots to be able to go add a property and now all that we have to do is go add something onto the end of the record parameters that we're passing in to the primary Constructor so now that you can see what a primary Constructor looks like on a record let's look at some code that's using the record you can see here online 8 I'm instantiating a new instance of the record type and I'm using a little bit more of a modern syntax here where we can have just the type on the left side and then instead of having to type again we can just use the word new another variation of doing this is being able to write record with VAR in front of it and then doing new my record so a lot of people aren't really a big fan of using VAR personally I am but now that we have this other type of syntax that we can write just like this and remove the type being duplicated a second time here on line 8 I find this is pretty clean but what this line is doing is creating a new record it's passing in one for the ID and then hello for the name so seems pretty straightforward and pretty simple what this does not allow you to do though is mutate those properties so if you watch me uncomment these lines Visual Studio will not allow you to assign new things onto those property values and that's because with the primary Constructor on a record those effectively become readon they effectively only get instantiated by the primary Constructor but what we can do with those properties is read from them they are readonly properties so we can go run something like a console right line and read those values off with no problem so that's what the record type primary Constructor functionality looks like it essentially gives us readon properties that we only have to declare inside of the primary Constructor and essentially it removes these two other locations we would have to go add information about the properties that we're trying to extend and generally we use records for something like a data transfer object right so not necessarily a like a class or something that has a lot of logic in it it's not um performing algorithms but it's something that's holding data that we transfer around inside of application so some type of state or result information that we can move between different parts of our application so in my opinion primary Constructors on records are an awesome addition uh just having a record with primary Constructors is great when records were released they came with this so this is not net new uh but this is what happened when we got records and I'm going to go back to the code and we're going to start looking at classes and I'll show you the classic version of a class that's a lot of class and then I'm going to show you the primary Constructor version so we'll see how that looks and then dive a little bit deeper all right now that we're back in the code here we're going to look at the classic Constructor class as the next example so if I go expand this this is going to look basically the exact same as the class that I wrote out by hand a little bit earlier for us when we were considering what my record would look like as a class but instead of focusing on what's written right here let's scroll up and see how we can use this class the way that we would go instantiate this class is essentially the same way that we did the record so all of the syntax is very much the same and the fact that we have this code here not able to compile if we uncomment it is the exact same as the record and in fact just like we saw with the record we're able to read these property values off so truly declaring a class just like this is the same as having the record and I wanted to show this code just to illustrate that the functionality is essentially the same between these two things the record type is going to allow for some other stuff that we're not showing right here but when it comes to these properties and being passed in either in the record as the primary Constructor or with the class in this old school way that we were used to using by passing in the parameters and then assigning them to the properties that are explicitly written out like this essentially the functionality is identical so identical functionality for the usage of these properties but when we have something like a record it's just a lot simpler to go right there's a lot less boilerplate code it makes it easier to write easier to read just quicker in my opinion everything about it better and now we're going to get into the part that people can't seem to agree about we're going to talk about primary Constructors on classes and this was introduced in C 12 so this is pretty new depending on when you're watching this video now we're going to go over to visual studio and see how this works but I wanted to call out why I think people have a bit of a challenge with this and primary Constructors on classes don't behave the exact same way that they do on records and personally I think that's people's biggest beef with it like if you're going to introduce it why not make it behave the same way and I kind of get it and I kind of don't so I can understand why there's a bit of confusion and maybe a little bit of frust around the usage but let's go check out how they work I'm going to show you the pattern the same way that we looked at the other ones except with the primary Constructor for a class and then I'm going to show you a different way that I like to use primary Constructors and why I think they work really well all right back in the code here you can see that I have something waiting for us down here so don't look cover your eyes but this part is the one we're going to look at with the primary Constructor and yet again more surprises waiting for us so this syntax might be shocking to some people if you haven't used it yet cuz it's a little bit weird but essentially with a class now this is not a record with a class we get a primary Constructor which if I scroll back up to the record it looks very similar right we end up having this syntax right after the name of the type so very cool and I just wanted to show you that in a method we can access the ID in the name so in here we do have access to these things being passed in however you'll notice I'm going to go expand this classic Constructor class now that we didn't have to do this type of assignment right we didn't have to go declare the properties we didn't have to do this assignment if I Collapse it again you can see that we just get them we just have access to them right in the Constructor like this which is nice because it's saving that boilerplate code that's one of the advantages that we had with the record and now we're able to get some of that on these primary Constructors for classes so I'm going to come back to this yikes comment in just a moment but I want to scroll up and show you how this is used to compare it to the other examples so we end up declaring and instantiating a primary Constructor class just like we did the other types so this part the exact same now when we go down to look at the properties just like we were doing for the other examples that we looked at if I uncomment this it doesn't work as well but the reason that it doesn't work is different and that's because when we use a primary Constructor on classes we're not getting properties for name and ID we're getting Fields so that behavior is different between records and classes with the record if I scroll back up here we end up getting these two properties these are things that exist but we can't write to them that's why we're able to do this here right so that that works on the record they become readon properties but but you'll notice that this next part that I wrote here this also doesn't work with a primary Constructor on a class not only are they not writable but we can't read them and that's because there's no actual properties created so what does that mean well if we scroll back down we can see inside of the print method that we have ID and name they're visible inside here so they're just like having Fields but there's one more catch that makes these a little bit different than the records and that is that these are not read only and that's where this yikes is going to come in because what we're able to do is assign things after so with primary Constructors in the other cases we got this ability to have initialize only properties and not only are these ones not properties they're Fields they're also totally mutable so at any point we can go do an assignment like this and it works so these are two different behaviors that we get between records having primary Constructor and classes having primary Constructors and like I said before I jumped into the code I think that that's the big concern that people have and I understand that right that's kind of frustrating it's kind of weird it's like why do we have different behavior for these things that look very much the same even though a record in a class are slightly different um it just seems a little bit odd that the behavior that we would get out of the two things doesn't line up but I don't let that bother me because I think the way that I would use primary constru s doesn't necessarily interfere here I would not use primary Constructors for things like I was showing you with the class I'm not creating classes like that that feel like dto because I would just use a record for that I am using primary Constructors for other types of things where the concept of being able to mutate something just doesn't make a lot of sense like it's not going to come up ever on something like a dto especially if you're jamming a lot of stuff on it you might find find yourself in a situation where you can essentially mutate a property and because you can it might happen due to a bug or something else but the way that I've set up my things which I'll show you in just a moment I don't really have to worry about that so let's go check that out all right the way that I use primary Constructors looks something a little bit like what you see on the screen right now I've left out all of the implementation details of things like this because it doesn't really matter but what you can see kind of starting if we think about layers in an application I might have something like my business logic in a class right and it takes in a dependency that's my repository so I want you to think about like a really simple layered architecture here where we have business logic and then a repository and then the actual connection that we use to be able to access that repository so our business logic and take in this dependency and you can see that I'm using an underscore naming pattern here because I treat these exactly like Fields right I would do something like my repository and then suggest that it needs to do something here and it would be just like a field and it doesn't seem weird to me when I go to call this from the outside because I essentially am never calling Constructors on things anymore at least not like this if I have logic in a class somewhere I'm doing almost all of this through dependency injection so this class would be registered to a dependency container and I'm never typing the words new my business logic anywhere in the code base so it doesn't feel weird for me to have a field defined like this and also the likelihood of me writing something like my repository and setting it to null is basically zero that would be kind of ridiculous for me to write even though it's possible but the other thing is that I'm never instantiating objects like this anyway I don't do that kind of thing because it breaks a lot of my design approach for being able to unit test so the fact that I only leverage things like this with dependency injection makes it such that primary Constructors work pretty well if I scroll up and we look at the my repository thing it's the same idea with a connection Factory so yes I could end up assigning connection Factory to null in some code that I might write in here somewhere but that never really comes up I don't have a need to ever go set something to null even though it's possible and because instantiating things inside of a class at least things that perform logic goes against a lot of my test principles that means I'm not going to risk running into that either so the downside of these things being mutable just doesn't really affect me personally so to recap super quick when I use primary Constructors it's essentially on records so data transfer objects and exclusive to that or I'm using primary Constructors now on classes that have logic I Don't Really Ever Write classes now that are data transfer objects and use primary construct instructors and it's for the reasons that I talked about having mutability sort of by accident or the fact that it can exist on a class when you use primary Constructors seems to feel really weird to me if you're using it as a data transfer object I feel like it's just a potential risk where something could go wrong however using primary Constructors on these other types of services or components that I might be building out that perform logic if I wire that all up with dependency injection it just feels like it's working magically and I'm getting rid of these big Constructor blocks that essentially just repeat the field definitions anyway so I'm removing basically uh half of the amount of fields that I would have to go to Clare and duplicate in the Constructor so I'm removing almost 2/3 of what I would have to write in terms of passing things in because we would have the field declaration we would have the parameter list in the Constructor and then we would have the assignment that happens inside the Constructor two of of those go away I almost put up the wrong finger there by accident but two of those go away and we're just left with passing them in through the Constructor it is the field definition at the same time now what I would love to see is having something like a readon keyword being put in front of that and that way we could explicitly say to the compiler I want this to be a readon field when you go make it I'm sure I'm not the first person to think about that and I know the net team is filled with super smart people so I'm sure there's a good reason why they can't do that yet or they have not done it yet but I'm hoping that they'll introduce something like that because I think that that would just clean it up and give us the behavior that we would expect but I'm curious to see what you think are you using primary Constructors only with records or are you starting to dabble with them in classes as well let me know in the comments and that's it for this video so thanks for watching and I'll see you next time

Frequently Asked Questions

What are primary constructors in C# 12 and how do they differ between records and classes?

Primary constructors in C# 12 allow you to define parameters directly in the class or record declaration, which simplifies the syntax and reduces boilerplate code. The key difference is that in records, these parameters create read-only properties, while in classes, they create mutable fields. This distinction can lead to confusion, as the behavior is not consistent between the two.

Why do some developers have issues with primary constructors on classes?

Many developers find it confusing because primary constructors on classes do not behave the same way as those on records. Specifically, while records create read-only properties, classes create mutable fields. This inconsistency can lead to frustration, especially for those who expect similar behavior from both constructs.

In what scenarios do you prefer to use primary constructors?

I prefer to use primary constructors primarily with records for data transfer objects, as they provide a clean and concise way to define immutable data structures. For classes, I use primary constructors when I'm building components that perform logic, especially when leveraging dependency injection, as it helps reduce boilerplate code and keeps my codebase cleaner.

These FAQs were generated by AI from the video transcript.
An error has occurred. This application may no longer respond until reloaded. Reload