10
\$\begingroup\$

I understand that creating public properties that control private fields is good practice because of coupling and encapsulation, although lately I have seen it as such a waste of boilerplate code for a simple entity class like below.

Take this class for instance,

public class PersonEntity
{
    private string _firstName;
    private string _lastName;
    private int _age;

    public class PersonEntity(string firstName, string lastName, int age)
    {
        _firstName = firstName;
        _lastName = lastName;
        _age = age;
    }

    public string FirstName
    {
        get { return _firstName; }
        set { _firstName = value; }
    }

    public string LastName
    {
        get { return _lastName; }
        set { _lastName = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }
}

If all I am using is the property, is it okay to refactor the class to look something like this?

public class PersonEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }

    public class PersonEntity(string firstName, stirng lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

Or maybe even this is more simple (using object initialization to construct):

public class PersonEntity
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

Which one should I implement if all I want to use the entity for is to map data from a database, and use it as a model to write to the database at a later date than when it was created?

Which versions should I use, when should I use them, and for what reasons should I use each one?

\$\endgroup\$
3
  • 12
    \$\begingroup\$ The public T Name { get; set; } properties are called Auto-Implemented Properties. The compiler creates the private backing field for you so they are just a convenience. I'd say 100% use it for simple properties like you've shown. \$\endgroup\$
    – RobH
    Commented Sep 30, 2020 at 9:10
  • \$\begingroup\$ Note that auto-implemented properties were added to C# 3.0. So you may see needlessly-explicit members in code that was written to target earlier C# versions, or by people who learned C# then and never bothered to update their knowledge. \$\endgroup\$
    – Jonathan
    Commented Oct 1, 2020 at 10:59
  • \$\begingroup\$ Actually that is not really encapsulation but pseudo object orientation. I would refactor it like you mentioned it. \$\endgroup\$
    – Welcor
    Commented Oct 1, 2020 at 22:33

2 Answers 2

12
\$\begingroup\$

There is a special use case when the (slightly modified) second approach could be beneficial. Namely when you want to create immutable objects.

public sealed class PersonEntity
{
    public string FirstName { get; }
    public string LastName { get; }
    public int Age { get; }

    public PersonEntity(string firstName, string lastName, int age)
    {
        FirstName = firstName;
        LastName = lastName;
        Age = age;
    }
}

By removing the setters you are not allowing the consumer of the class to alter its data after creation. In case of concurrency immutable structures can be shared safely between multiple workers without extra syncronization.

You can define with method(s) to this class as an extension to provide copy with alternative value functionality.

public PersonEntity WithFirstName(string firstName)
{
    return new PersonEntity(firstName, LastName, Age);
}

public PersonEntity WithLastName(string lastName)
{
    return new PersonEntity(FirstName, lastName, Age);
}

public PersonEntity WithAge(int age)
{
    return new PersonEntity(FirstName, LastName, age);
}

Of course you can make it a bit more generic:

public PersonEntity With(string firstname = null, string lastName = null, int? age = null)
{
    return new PersonEntity(
       firstName ?? this.FirstName, 
       lastName ?? this.LastName, 
       age ?? this.Age);

}

Usage sample:

var xy = new PersonEntity("x", "y", 15);
var xz = xy.With(lastName: "z");
var xz25 = xz.With(age: 25);

UPDATE: C# 9's record
In C# 9 there will be a new construction called record.

Declaration

public record PersonEntity
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
    public int Age { get; init; }
}

Usage

var xy = new PersonEntity { FirstName = "x", LastName = "y", Age = 15 }; 
var xz = xy with { LastName = "z" };
var xz25 = xz with { Age = 25 };
\$\endgroup\$
2
  • \$\begingroup\$ @symaps Thanks for spotted a typo. :D \$\endgroup\$ Commented Sep 30, 2020 at 15:49
  • 3
    \$\begingroup\$ +1 for the record suggestion, but do note that the behaviour of records is fundamentally different (they provide value equality instead of reference equality) \$\endgroup\$
    – jeroenh
    Commented Sep 30, 2020 at 18:33
4
\$\begingroup\$

The refactor with an explicit constructor is a drop-in replacement for the original code and would be the direction I would go.

The code requiring object initialization requires a change to how objects are constructed and may break code elsewhere in the project.

In my opinion, I wouldn't bother changing it unless and until another change is needed. Although the code is unnecessarily verbose, it is easily understood and does not have any other code smells of concern. I wouldn't consider this refactoring a good use of time. I would instead focus on other refactorings that can provide a bigger benefit.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.