0

I'm facing a dilemma that's very common in C# (and other OO languages) where I'm trying to meet 3 competing requirements :

  1. I absolutely don't want the end-user of my class to forget to initialize some of the fields, at construction time.
  2. I want a deserializer to be able to instantiate that class and populate the fields without silently being "blocked" by a private setter.
  3. 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 :

  1. It doesn't deal with requirement #2 (expose fields for deserialization)
  2. 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+)

jeancallisti
  • 1,046
  • 1
  • 11
  • 21
  • 1
    Look at the [`required` modifier](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required). You can also fulfill all of your requirements with a record: `public record MyClass(bool Field1, IEnumerable Field2)`. – CodeCaster May 17 '23 at 09:38
  • The duplicate does answer all your questions. Even though the question might be 10 years old, if you scroll down, you'll read more answers mentioning `init` and `required` and records. See also https://stackoverflow.com/questions/62270834/use-system-text-json-to-deserialize-properties-with-private-setters. – CodeCaster May 17 '23 at 09:47
  • @CodeCaster the word "deserialization" does not appear anywhere, therefore that aspect of the issue is not discussed nor indexed, therefore it's not searchable. I will not say more, thank you for your dedication. – jeancallisti May 17 '23 at 09:50

1 Answers1

1

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 records (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.

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
  • 1
    I like your enthusiasm and prolificacy in posting answers, but please search for duplicates first. There's enough knowledge spread out over multiple Q&As already. – CodeCaster May 17 '23 at 09:43
  • @CodeCaster see my explanation of why this is not really a duplicate. – jeancallisti May 17 '23 at 09:46