13

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
  • 438

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,848
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
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.

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.