I'm facing a dilemma that's very common in C# (and other OO languages) where I'm trying to meet 3 competing requirements :
- I absolutely don't want the end-user of my class to forget to initialize some of the fields, at construction time.
- I want a deserializer to be able to instantiate that class and populate the fields without silently being "blocked" by a private setter.
- I don't want any non-initialized fields left hanging (e.g. a list has to be constructed, no loose
null
in there!)
Note:
This is NOT the same question as this old one : How to make a property required in c#? for two reasons :
- It doesn't deal with requirement #2 (expose fields for deserialization)
- It's 10 years old, and C# has evolved a lot since then.
So, let's say that this is my class :
public class MyClass {
public bool Field1 {get; set;}
public IEnumerable<bool> Field2 {get; set;} = default!;
}
Note : don't obsess over the = default!
bit, here I'm just making the compiler happy (it detects non-initialized fields) to move on to the core of the issue.
if I needed to meet requirement #1 only then I would NOT do this :
var o = new MyClass() {
Field1 = true
// Uh oh, I forgot Field2
};
Instead I would do this :
public class MyClass {
public bool Field1 {get; private set;}
public IEnumerable<bool> Field2 {get; private set;}
public MyClass(bool field1, IEnumerable<bool> field2) {
Field1 = field1;
Field2 = field2;
}
}
For requirement #3 only I would do this :
public class MyClass {
public bool Field1 {get; private set;}
public IEnumerable<bool> Field2 {get; private set;} = new List<bool>();
public MyClass(bool field1) {
Field1 = field1;
}
}
// ...
c.Field2.AddRange(...);
I would NOT do this because despite protecting the fields from tampering, it does not guarantee requirement #1 :
public class MyClass {
public bool Field1 {get; init;}
public IEnumerable<bool> Field2 {get; init;} = new List<bool>();
}
But now I have to fullfil requirement #2 . That's a problem because the deserializer cannot populate protected
or private
fields.
Which means that this would NOT work, as Field1
would remain false
:
(note: we're assuming that the deserializer is properly configured : no upper-case/lower-case nonsense or whatnot)
string json = @"{ ""Field1"": true, ""Field2"": [] }";
var o = JsonSerializer.Deserialize<MyClass>(json);
Assert(o.Field1 == true); // fails
The only solution I'm aware of to fulfill all 3 requirements is a constructor that has ALL the fields :
public class MyClass {
public bool Field1 {get; private set;}
public IEnumerable<bool> Field2 {get; private set;} = new List<bool>();
[JsonConstructor] // <-- to make it air-tight!
public MyClass(bool field1, IEnumerable<bool> field2) {
Field1 = field1;
Field2 = field2;
}
}
string json = @"{ ""Field1"": true, ""Field2"": [] }";
var o = JsonSerializer.Deserialize<MyClass>(json);
Assert(o.Field1 == true); // succeeds
even that solution is not perfect, because if I add a field "Field3" to the class then I might forget to add it to the exhaustive constructor, and the deserialization would silently ignore it. Fail!
My question :
Is there an elegant way of achieving this in modern C#? Many answers about this topic are 10+ years old. C# has made a ton of progress in every direction since then. Ideally, I'd like an answer for .Net6 (C# 10) AND possibly an answer for more recent C# (C# 11+)
For C# 11 - there is feature introduced to specifically support such scenarios - required
modifier which allows your to do something like (also note the usage of init
keyword which declares an init-only setter which allows to assign a value to the property only during object construction):
public class MyClass
{
public required bool Field1 {get; init;}
public required IEnumerable<bool> Field2 {get; init;}
}
The last option can also be reduced to using record
s (available since C# 9):
public record MyClass(bool Field1, IEnumerable<bool> Field2);
P.S.
Note that required
modifier is also considered by System.Text.Json
, so marking property with it will make the corresponding JSON property required in the JSON payload.