Avoiding Default Constructors and Public Objects

I am working on a project with SignalR, and I have some objects that I am going to go through it. These objects are only explicitly created in my internal code, and I really would like to provide immutability and invariants on them. I am facing a problem that SignalR requires me (well, really, NewtonSoft.Json) to have default constructors, no-args and public setters for my properties so that it serializes and deserializes them over the wire.

Here is a contrived example:

public class Message{ public string Text {get;set;} public int Awesomeness {get;set;} } 

What I would like is something more along these lines (it should probably only have full read-only fields, and getter-only properties are completely immutable, but for something that is just POCO without any methods good enough)

 public class Message { public string Text {get;private set;} public int Awesomeness {get;private set;} public Message( string msg, int awesome){ if (awesome < 1 || awesome > 5){ throw new ArgumentOutOfRangeException("awesome"); } Text = msg; Awesomeness = awesome; } } 

If I do this, my object cannot be deserialized by the SignalR.NET client library. I can just click on the default constructor there and make my setters public, but then I must remember that I did not use them in my code, and make sure that no one in the team uses them without understanding.

I started using this idea to mark the default constructor as something that should never be explicitly used:

 [Obsolete("Bad! don't do this!") public Message(){} 

But I can not use the Obsolete attribute only for the property installer.

If I really wanted to, I could select the “real” object from the DTO view and convert between them, but I'm really not crazy to write a bunch of templates to do this, and introduce another layer.

Is there something I'm missing out on, or do I just need to bite a bullet and deal with it?

+2
source share
1 answer

If your class does not have an open constructor without parameters, but has a single public constructor with parameters, Json.NET will call this constructor, matching constructor arguments to JSON properties by name using reflection and using default values ​​for the absence of the property. Matching by name is not case sensitive unless there are several matches that differ only in that case, in which case the match becomes case sensitive. Thus, if you just do:

 public class Message { public string Text { get; private set; } public int Awesomeness { get; private set; } public Message(string text, int awesomeness) { if (awesomeness < 1 || awesomeness > 5) { throw new ArgumentOutOfRangeException("awesome"); } this.Text = text; this.Awesomeness = awesomeness; } } 

You will be able to serialize and deserialize your class using Json.NET.

Prototype fiddle .

If your class has several public constructors, all with parameters, you can mark the one that will be used with [JsonConstructor] , for example:

 public class Message { public string Text { get; private set; } public int Awesomeness { get; private set; } public Message(string Text) : this(Text, 1) { } [JsonConstructor] public Message(string text, int awesomeness) { if (awesomeness < 1 || awesomeness > 5) { throw new ArgumentOutOfRangeException("awesome"); } this.Text = text; this.Awesomeness = awesomeness; } } 

See also JsonSerializerSettings.ConstructorHandling , in which Json.NET indicates whether to prefer a non-primary non-gap constructor over a single open constructor with parameters.

+4
source

Source: https://habr.com/ru/post/1014453/


All Articles