Which object creation method is more used?
I've started learning C# recently and I am wondering which way of object creation is more acceptable and used, using specific constructors or object initialization syntax? Are there any tradeoffs between the two?
17 Replies
Both are used
Both are used, imho, it depends on what type or kind of
object
it is. For Instance, if it's a Model type that can have a lot of properties I won't create a constructor for each permutation possible, also a constructor with optional defaults doesn't make sense, that's why you use object initialization.
On the other side there's a working class with dependencies, e.g. HttpClient
, so you wanna have a constructor for this.Unsure of the standards for this, but I lean towards object init syntax, because it results in more readable code: new Thing { A = 1, B = 2 } vs new Thing(1,2)
But there are some things you can't really do with init syntax, without things getting weird, like if (for example) you need to fill in C = A+B. A constructor can do that if supplied constructor arguments, but the constructor occurs before object init syntax, so if you do new Thing { A = 1, B = 2}, the constructor can't set C = A + B, because at the time constructor runs, A and B aren't set yet
also object init syntax isn't very viable unless using a reasonably modern C# version that offers required and init keywords, without those, it can be a sort of antipattern because you can't enforce required values like you can with a constructor (except through runtime validation)
No it ain't, it definitely depends on the use-case - every misuse of something can result in bad results.
https://scotthannen.org/blog/2021/04/19/partial-optional-object-population.html if you can't enforce which values are required, you generally end up with this. If your devs are good and pay attention, you won't, but the entire point of a strongly typed language is to force devs to do the right thing and reduce the possibility of failure (at compile time, rather than runtime)
Don't POOP - The Partial/Optional Object Population Anti-Pattern
The Partial/Optional Object Population (POOP) anti-pattern occurs when have a class with multiple properties,we re-use it in various parts of our application...
so I personally wouldn't recommend object initializers if you don't have required/init keywords available, it may as well be a dynamic if you can't guarantee which properties actually have data
To make it clearer, you have an
object
, let's say Student
:
That object
or record
can be filled througout the runtime, not at one place, so enforcing Forename & Surname at object creation is flawed.
You would rather use some kind of Validation
to ensure the records integrity before inserting into a db.If the Student doesn't yet have a Forename and Surname, it's not yet a Student
if you have some specific need to deal with some partial version of a student with just an Age, then make a model for that need, don't try to force the Student model to be something it isn't. You may end up passing it to a method that requires a Student, and expects it to have a Forename and Surname
Particularly because most methods people write usually take a single parameter; if a method takes in a Student, and there isn't any required/init keywords, validation has to occur at runtime and you lose out on compile time errors that would exist if the method instead took in a (string Forename, string Surname, int Age). Using a model as a parameter like that kills compile time safety unless it can be validated at compile time
(and using methods like that is important for things like APIs, so rather than writing full method signature without the models in them, it's better to make the models compile time validated)
Semantically you ain't and that's why we have plausibility-gates (
if(student.Forname == null)
).
If there's a Form you need to fill out page by page, you don't create an object for each page, you create an empty object and validate throughout and at the end.that kind of runtime validation isn't required if you use required/init keywords. And yes, I think it's typical practice that you'd make an object per page, a DTO, which when finished, you combine all those DTOs into one database object that is fully populated
I've run into far too many problems with POOP, where we have a bunch of methods that all take a Student, but they all require different things in a Student to be filled - and there's no way to tell what they require without navigating to the definition of the method and reading the validation. When C# already has compile time validation, to make sure that when you call a method you give it the correct data it needs to run the method, and won't even compile if you do it wrong
But industry standard has migrated toward methods that take in one model as a param, instead of multiple params, which defeats the purpose of a method signature if you aren't validating the model at compile time
I've been stewing on this concept for a while :lul: I get very annoyed that most companies and standards have effectively broken the concept of method signatures, where you can no longer be sure if a method call will succeed or not based on the data you passed it
If implemented correctly https://learn.microsoft.com/en-us/dotnet/api/system.componentmodel.dataannotations.ivalidatableobject?view=net-8.0
will give you the exact properties missing or and that's even more important mis-set, yet another security layer that won't let you import garbage into the db.
I can't say POOP, imho the partial creation is pretty fine as long as you keep it sane.
IValidatableObject Interface (System.ComponentModel.DataAnnotations)
Provides a way for an object to be validated.
As an example, I recently changed some work code to make some db model properties required.
Someone came to me and asked what to do because he was doing
Someone came to me and asked what to do because he was doing
.Select(x => new Student { Id = x.Id})
and it wasn't valid. I pointed out that what he's selecting is just an ID, it's not a Student. If he passed that object to any method that accepts a Student, that method would fail, and the compiler wouldn't even tell him that it was going to fail. The data doesn't meet the contract of what a Student is, and so it's appropriate that the compiler doesn't let you create a Student from that data
required/init is there to prevent you from misusing the models like that
(that said, EFCore does give you some valid situations where you might want to do something like that, but even those are usually bad practice, such as using Attach on the entity - usually a bad idea as opposed to just querying the change-tracked entity and updating values on it)Only if validation is implemented - required alone is limited and security for NOT NULL
fair, you do still need validation in most cases, such as validating lengths of strings or whatever, so it's not perfect compile time safety. But it's the same compile time safety as a real method signature would be (as opposed to a method signature just taking the model as a single param), which is better than nothing
I guess TLDR is that it can make dev slightly more tedious, but safer, which is sorta the idea behind a strongly typed language in the first place. And it invalidates a lot of bad practices that are common, which does usually mean extra work to fix those bad practices, but... they were kinda bad in the first place
There's no good reason to do
.Select(x => new Student { Id = x.Id})
instead of .Select(x => x.Id)
, but it's extremely commonIdd, I'm not saying it's bad, especially when using something highly abstract like EF. - But back to topic or to the basics, when not using any ORM.
Yeah, ORMs and EF make it really awkward and you gotta think long and hard about using required/init and even NRTs. Without an ORM, it's a lot more straightforward
Speaking of the actual topic, also worth mentioning, object initializers are more robust to changes. If you add or change the parameters of a method signature or constructor, call sites might still be compile-time valid even if they're now passing the wrong data. But if you change which properties are required, or rename properties, or add new required properties, object initializers will get renamed/updated automatically and will always show call sites that are no longer valid
Ex, Student(string Forename, string Surname), which you then change to Student(string Surname, string Forename) - you've accidentally broken everywhere that's using that constructor, and the compiler isn't going to tell you about it
Yeah, in a traditional approach everything can lead to errors if misunderstood (especially by the team, if no docs available), but again, on the other hand traditionally we have plenty of countermeasure mechanisms, even simple unittest which also working as an example to any co-worker.
We could go on and on, the next topic will be the
Nullable
project property which I keep disabled and imho creating an annoying overhead for anyone who knows what they are doing and sticking to TDD and other principle in the first place, but that's something for #chat 🙂
Personally I prefer it traditional, not too abstract, it all became way too abstract already imho, but maybe I'm just old.