12

Update: C# 9 should fix this problem using records :D


I was wondering if there is a recommended approach to initializing the properties of a plain object that is used for data transfer, for example via a REST-API.

Here are two variants I can think of:

public class Dto
{
    public string Name { get; set; }
    public int Number { get; set; }
}

new Dto {
    Name = actualName,
    Number = actualNumber
};


vs


public class Dto
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Dto(string name, int number)
    {
        Name = name;
        Number = number;
    }
}

new Dto(actualName, actualNumber);
or
new Dto(name: actualName,
        number: actualNumber);

I would go for the first, since it's shorter and requires less work to maintain. However, newly added properties could be overlooked by someone filling the DTO. I am also aware that it does not make the object immutable, but since it's only a DTO I personally find that to be less important.

AyCe
  • 378
  • 1
  • 3
  • 13
  • Think about how you will get data from a database or rest request into this object. Will you write a bunch of mapping code? Are you using a tool that requires getters and setters? Or one that does injection into a constructor? How will you get from JSON, and HTML form, a database result set to this? – joshp Sep 07 '17 at 05:56
  • @joshp The API will fill those DTOs up with the results of the performed action. These can come from a database or other code. JSON might not be the only protocol format the DTOs travel on, and this might not be web-related in every case (when communicating between C# apps using SignalR for example). – AyCe Sep 07 '17 at 06:13
  • So, for JSON, have you picked which library or tools you will use to deserialize into the DTO? Same question for database results. Are you going to use a serializing tool that requires a no-args constructor + setters&getters? Are you using one that constructs objects entirely through reflection using non-public setters if necessary? Constructor injection? Consider what your tools will need. – joshp Sep 07 '17 at 07:24

4 Answers4

9

My thoughts are that if your object serves as a means to transfer data (and therefore purely output/input in a sense), the better thing is to not render objects mutable if they need not be and that this overrides readability when it is a toss up like this. The logic being that the absence of public setters says more about what purpose the class serves and therefore is more readable than simple syntax could provide.

In other words, your class should be like your second:

public class Dto
{
    public string Name { get; private set; }
    public int Number { get; private set; }

    public Dto(string name, int number)
    {
        Name = name;
        Number = number;
    }
}

And if we're being frank, new Dto(actualName, actualNumber); isn't that bad at all from a readability standpoint.

Neil
  • 22,670
  • 45
  • 76
  • Normally I would agree with you. This class will however also be used on the receiving side so it might not work without a standard constructor and settable properties, depending on the serialization library/method and way of transmission. Did you mean "isn't" in your last statement? – AyCe Sep 10 '17 at 04:32
  • @AyCe Ah, yes, good catch. I did mean "isn't". Also, the class has everything necessary to copy or serialize without letting setters be public. I wouldn't be tempted to let them be public on the off chance that the receiver cannot handle an immutable class, unless I absolutely had to do it that way because doing otherwise would require several days of refactoring. – Neil Sep 11 '17 at 07:38
  • 2
    In C# 6.0 and above you can you remove the setter entirely instead of making it private. Earthwise this is a great answer. – Aluan Haddad Sep 11 '17 at 20:14
3

C# 9.0 Records and init-only setters make this very elegant.

It can be done as simply as this:

public record Dto
{
    public string Name { get; init; }
    public string Number { get; init; }
}

or if you need default values you can do this:

public record Dto
{
    public string Name { get; init; } = "default value"
    public string Number { get; init; } = "default value"
}

Check out this post on the subject:

https://www.tsunamisolutions.com/blog/c-90-records-and-dtos-a-match-made-in-redmond

Lewis
  • 31
  • 2
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-ask). – Community Sep 16 '21 at 02:06
1

You are looking at a concrete class only. Expand your view by adding an interface.

That interface has read-only properties, without any setters. When some method receives the instance but knows the interface only, it cannot change the instance. But at those places where the instance is created, the concrete type is known, hence parameterized constructors (which I prefer for clarity) or public setters (if required by some serialization framework) can be used there nonetheless.

Do NOT add the setters to the interface, they'd cause mutability outside the area of instance creation.

Bernhard Hiller
  • 1,953
  • 1
  • 12
  • 17
0

Your DTO must be serializable to be useful, and one of the requirements for serializable is having a default public constructor (because serializers don't know how to call a constructor that takes parameters).

In your default public constructor you should initialize properties with default values. When you deserialize to an instance, properties that are not present in the serialized representation will have these default values.

bikeman868
  • 879
  • 6
  • 9
  • 4
    At times it may be suitable to have an private empty constructor which the underlying serialization framework can use, and constraint the instansiator of the class to pass arguments via the constructor to gain immutability. – Yuval Itzchakov Sep 07 '17 at 12:11