Antes de entrar en materia, vamos a explicar qué son los backing fields.
Los campos de respaldo (backing fields en inglés) son campos privados que almacenan los datos expuestos por una propiedad pública.
Fields – Microsoft Docs (2021)
Cuando empezamos a programar clases en C#, lo que se enseña es que debes poner a los atributos de tu clase pública un «get; set;» y listo. Algo como lo siguiente:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public DateTime RegistrationDate { get; set; }
}
Esto es normal, al fin y al cabo las guías lo que procuran es simplificar la curva de aprendizaje inicial. Sin embargo, al no explicar lo que esto implica, por qué se hace y por qué podrías no querer hacerlo así, perdemos herramientas muy interesantes que nos pueden ahorrar repetir mucho de nuestro código. Comencemos por el principio:
¿Qué significa el «get; set;»?
Esto es lo que conocemos como parte del Syntax Sugar de C#, básicamente un atajo que nos da C# a los convencionales Getters y Setters que harías en otros lenguajes como Java. Cuando el compilador se encuentra este tipo de código, lo traduce de la siguiente manera:
public class User
{
private string _name; // Backing field donde guarda los valores del set.
public string Name {
get
{
// Lo que devuelve cuando llamamos a string name = user.Name
return _name;
}
set
{
// Lo que hace al asignar un valor. Por ejemplo: user.Name = "Victor"
_name = value;
}
}
}
Imaginate tener que hacer eso para cada atributo que tuvieras en tus clases… Ciertamente el «atajo» de C# es muy conveniente 💯👍. Sin embargo, usar el atajo puede también traernos inconvenientes. Por ejemplo, si sé que voy a enseñar en nombre completo de usuario usando Nombre + Apellidos, es muy posible que la primera solución que se me ocurriera sea sencillamente hacer esa conversión en la interfaz de usuario.
Para ejemplificarlo, voy a usar un componente que fuera una tarjeta hipersimplificada de un post. Debajo del título del post, quiero que aparezca siempre el nombre completo del autor. Suponiendo que creamos una tarjeta como la siguiente (generada con Material Design) y usando una arquitectura MVC, se podría ver algo similar a lo siguiente:

<div class="post-card-content">
<a asp-area="Blog" asp-controller="Posts" asp-action="PostDetails" asp-route-id="@post.Id">
<h4 class="card-title">@post.Title</h4>
</a>
<pre class="card-details">
@($"{post.Author.Name} {post.Author.LastName}")
</pre>
</div>
¡Perfecto! Objetivo cumplido, ¿no? Pues si solo fuera en una sola zona, no estaría mal pero…
¿Qué ocurre si siempre que vayas a mostrar el autor quieres que salga su nombre completo?
Entonces tendrías que ir haciendo esa suma literalmente en cada ocasión que muestres el autor. Y esto puede llevar fácilmente a inconsistencias. Si tienes 20 sitios donde aparece el nombre completo del autor, es muy posible que hagas la modificación en la mayoría de sitios y se te olvide uno o dos 😅
Es aquí donde los backing fields podrían venirnos bien.
¿Por qué podríamos preferir un backing field?
Principalmente, para encapsular lógicas de negocio lo más cercano posible a nuestra clase, maximizando el principio DRY (Don’t Repeat Yourself). Abandonemos los campos «Name» y «LastName» del ejemplo anterior, convirtámoslos en backing fields y usemos una nueva propiedad llamada «FullName»:
public class User
{
private string _name;
private string _lastName;
public string FullName { get => $"{_name} {_lastName}"; };
public string LastName { get; set; }
public string Email { get; set; }
public DateTime RegistrationDate { get; set; }
}
Ahora podemos instanciar el nombre completo donde queramos llamando a user.FullName, evitando tener que repetir la conversión a la suma de los otros dos atributos cada vez que vayamos a usar el nombre. Usando el componente que vimos previamente:
<div class="post-card-content">
<a asp-area="Blog" asp-controller="Posts" asp-action="PostDetails" asp-route-id="@post.Id">
<h4 class="card-title">@post.Title</h4>
</a>
<pre class="card-details">
@post.Author.FullName
</pre>
</div>
Otros ejemplos de usos en backing fields
Hay muchas cosas que se pueden hacer con los backing fields, por ejemplo:
- Formateo de las fechas a dd/MM/yyyy pero conservando las horas, minutos y segundos en la base de datos.
public class User
{
private DateTime _RegistrationDate;
public string RegistrationDate { get => _RegistrationDate.ToShortDateString(); }
}
- Cuando no necesitamos el acceso al valor directamente pero si una interpretación del mismo según nuestra lógica de negocio.
public class User
{
private int _age;
public bool CanDrink{ get => _age >= 18; }
}
- Parecido al anterior, cuando queremos que el valor devuelto dependa de otra propiedad.
public class User
{
private int _numberOfChildren;
public string FamilySize
{
get
{
if (_numberOfChildren > 5) return "Big family";
else if (_numberOfChildren > 3) return "Medium-sized family";
else if (_numberOfChildren > 1) return "Small family";
else return "This family does not have any children.";
}
}
}
- Para validar y proteger los posibles valores que puede tener un campo siguiendo las lógicas de negocio.
public class Money
{
private int _cents;
public int RegistrationDate {
get => _cents;
set
{
// No tiene sentido tener menos de 0 céntimos
if(value < 0) throw new ArgumentOutOfRangeException();
_cents = value;
}
}
}
- Para añadir eventos adicionales bien al obtener los datos o al asignarnos a una propiedad.
public class Bank
{
private double _money;
public int Money {
get => _money;
set
{
if(_money == value) return;
_money = value;
// Avisar al usuario siempre que cambie la cantidad de dinero.
SendSavingsChangedNotificationToUser();
}
}
}