Records
En C#, los Records representan una evolución significativa en la manera de modelar datos inmutables dentro de arquitecturas modernas. Introducidos en C# 9.0, los Records son tipos de referencia con semántica de igualdad basada en el valor, lo que los convierte en una alternativa más expresiva y segura frente a las clases tradicionales cuando la inmutabilidad y la comparación por contenido son críticas. A diferencia de las clases, donde la igualdad suele ser referencial, los Records permiten comparar instancias basándose en sus propiedades, lo que simplifica algoritmos y estructuras de datos que dependen de la consistencia de valores.
En el contexto de desarrollo, los Records son especialmente útiles en aplicaciones orientadas a dominios (DDD), manipulación de datos, patrones CQRS y escenarios donde la inmutabilidad favorece la integridad de la arquitectura. Además, integran principios de la programación orientada a objetos (OOP) como encapsulación y herencia, pero añaden una semántica declarativa que facilita la escritura de código más limpio y eficiente.
En este tutorial aprenderás: la sintaxis esencial de los Records en C#, cómo integrarlos en estructuras y algoritmos, cómo evitar errores comunes en su uso, y cómo aplicarlos en proyectos reales dentro de arquitecturas backend complejas. El lector avanzará desde ejemplos básicos hasta patrones prácticos que permiten optimizar rendimiento, claridad y seguridad en aplicaciones C#.
Ejemplo Básico
textusing System;
public record Persona(string Nombre, int Edad);
public class Programa
{
public static void Main()
{
Persona persona1 = new Persona("Ana", 30);
Persona persona2 = new Persona("Ana", 30);
Persona persona3 = new Persona("Luis", 25);
Console.WriteLine($"persona1 == persona2: {persona1 == persona2}");
Console.WriteLine($"persona1 == persona3: {persona1 == persona3}");
// Uso de propiedades inmutables
Console.WriteLine($"Nombre: {persona1.Nombre}, Edad: {persona1.Edad}");
// Copia con expresión 'with'
Persona persona4 = persona1 with { Edad = 31 };
Console.WriteLine($"persona4: {persona4}");
}
}
En este ejemplo, definimos un Record llamado Persona
que encapsula dos propiedades: Nombre
y Edad
. Al ser un Record, automáticamente implementa igualdad estructural: dos instancias con los mismos valores son consideradas iguales. Esto se observa cuando comparamos persona1
y persona2
, resultando en true
, mientras que la comparación con persona3
devuelve false
.
La sintaxis public record Persona(string Nombre, int Edad);
es concisa y aprovecha el constructor posicional, lo cual evita escribir código repetitivo para inicialización y propiedades. Además, los Records fomentan la inmutabilidad: una vez creados, sus propiedades no pueden cambiar directamente, lo que reduce errores relacionados con estados inesperados en estructuras complejas o multithreading.
Un aspecto avanzado mostrado aquí es el uso de la expresión with
, que permite crear nuevas instancias basadas en una existente, modificando solo ciertos valores. Esto es especialmente útil en algoritmos que procesan colecciones de datos donde necesitamos generar nuevas versiones de entidades sin alterar las originales.
Este ejemplo muestra cómo los Records mejoran la legibilidad, reducen la cantidad de código boilerplate y fortalecen la semántica de igualdad basada en valor, aspectos esenciales en arquitecturas modernas donde la consistencia de datos y la seguridad son prioritarias.
Ejemplo Práctico
textusing System;
using System.Collections.Generic;
using System.Linq;
public record Producto(int Id, string Nombre, decimal Precio);
public class CarritoDeCompras
{
private readonly List<Producto> _productos = new();
public void AgregarProducto(Producto producto)
{
if (_productos.Any(p => p.Id == producto.Id))
{
Console.WriteLine("Producto ya en el carrito. Se omite duplicado.");
return;
}
_productos.Add(producto);
}
public void MostrarCarrito()
{
foreach (var producto in _productos)
{
Console.WriteLine($"ID: {producto.Id}, Nombre: {producto.Nombre}, Precio: {producto.Precio:C}");
}
}
public decimal CalcularTotal() =>
_productos.Sum(p => p.Precio);
public CarritoDeCompras AplicarDescuento(decimal porcentaje)
{
var nuevosProductos = _productos
.Select(p => p with { Precio = p.Precio * (1 - porcentaje) })
.ToList();
var nuevoCarrito = new CarritoDeCompras();
foreach (var producto in nuevosProductos)
nuevoCarrito.AgregarProducto(producto);
return nuevoCarrito;
}
}
public class Programa
{
public static void Main()
{
var carrito = new CarritoDeCompras();
carrito.AgregarProducto(new Producto(1, "Laptop", 1500m));
carrito.AgregarProducto(new Producto(2, "Mouse", 50m));
carrito.AgregarProducto(new Producto(1, "Laptop", 1500m)); // Intento de duplicado
carrito.MostrarCarrito();
Console.WriteLine($"Total: {carrito.CalcularTotal():C}");
var carritoConDescuento = carrito.AplicarDescuento(0.10m);
Console.WriteLine("Carrito con descuento aplicado:");
carritoConDescuento.MostrarCarrito();
Console.WriteLine($"Total con descuento: {carritoConDescuento.CalcularTotal():C}");
}
}
El uso de Records en escenarios prácticos, como un carrito de compras, permite crear modelos de datos inmutables y seguros. Aquí, Producto
es un Record con tres propiedades que representan un artículo único. La igualdad basada en valor asegura que dos productos con el mismo Id
, Nombre
y Precio
se consideren equivalentes, lo que simplifica la lógica de control de duplicados.
El método AgregarProducto
incluye validación para evitar añadir productos repetidos, demostrando buenas prácticas de integridad de datos. La función AplicarDescuento
ejemplifica el poder de los Records combinados con LINQ: utilizamos la expresión with
para generar nuevas instancias de productos con precios actualizados, garantizando que los objetos originales permanezcan intactos. Esto favorece la inmutabilidad, reduce riesgos de errores lógicos y facilita la depuración.
Además, este patrón es aplicable a escenarios más complejos como sistemas de facturación, gestión de inventarios o procesamiento de pedidos en arquitecturas distribuidas. El enfoque demuestra principios de OOP (encapsulación y reutilización) y optimización algorítmica, garantizando claridad y mantenibilidad.
Mejores prácticas y errores comunes en C#:
Primero, es esencial mantener la inmutabilidad de los Records, aprovechando su sintaxis declarativa y evitando mutaciones directas mediante reflexión o hacks que comprometan la seguridad. Además, conviene emplear la comparación basada en valor para optimizar algoritmos que dependan de la igualdad, como búsquedas en colecciones o deduplicación.
Errores comunes incluyen asumir que los Records son más eficientes que las estructuras (structs) en términos de memoria, lo cual no es cierto porque siguen siendo tipos de referencia. Otro error frecuente es el manejo inadecuado de excepciones al copiar objetos con la expresión with
, lo que puede generar inconsistencias si no se valida la entrada.
En depuración, se recomienda usar herramientas de profiling para identificar copias innecesarias que puedan afectar el rendimiento en sistemas de alto volumen de datos. Para optimizar, considera usar Records únicamente cuando la semántica de igualdad basada en valor y la inmutabilidad aporten beneficios reales, en lugar de usarlos como sustituto genérico de clases.
En cuanto a seguridad, evita exponer datos sensibles mediante el método ToString()
de los Records, ya que muestra automáticamente todas las propiedades. En contextos críticos, es preferible sobreescribir este método para controlar qué información se expone. Estas prácticas garantizan eficiencia, claridad y robustez en aplicaciones C#.
📊 Tabla de Referencia
C# Element/Concept | Description | Usage Example |
---|---|---|
Record posicional | Definición concisa de propiedades inmutables | public record Persona(string Nombre, int Edad); |
Expresión with | Copia de objeto con modificaciones | var nuevaPersona = persona with { Edad = 35 }; |
Igualdad por valor | Comparación de instancias según sus propiedades | persona1 == persona2 // true si valores iguales |
Herencia en Records | Permite jerarquías de datos inmutables | public record Estudiante(string Nombre, int Edad, string Curso) : Persona(Nombre, Edad); |
ToString automático | Muestra propiedades por defecto | Console.WriteLine(persona1); // "Persona { Nombre = Ana, Edad = 30 }" |
En resumen, los Records en C# ofrecen un enfoque moderno y seguro para modelar datos inmutables con igualdad basada en valor, reduciendo la complejidad de algoritmos y estructuras que dependen de consistencia. A lo largo de este tutorial exploramos desde la sintaxis básica hasta aplicaciones reales en sistemas de software, mostrando cómo se integran con LINQ, OOP y patrones de diseño orientados a backend.
El aprendizaje clave es que los Records no sustituyen a las clases, sino que las complementan en contextos donde la inmutabilidad y la comparación semántica aportan beneficios claros. Su aplicación práctica se extiende a arquitecturas empresariales, sistemas distribuidos y procesamiento de datos críticos.
Como próximos pasos, se recomienda profundizar en temas como init
properties, patrones de desestructuración, herencia avanzada en Records y su uso junto con colecciones genéricas. Además, explorar cómo los Records se integran con frameworks como Entity Framework Core o ASP.NET puede ampliar tu dominio en arquitecturas de backend modernas.
La práctica constante en proyectos reales y el uso consciente de mejores prácticas consolidará tu habilidad en C#, permitiéndote diseñar soluciones más limpias, seguras y eficientes.
🧠 Pon a Prueba tu Conocimiento
Test Your Knowledge
Test your understanding of this topic with practical questions.
📝 Instrucciones
- Lee cada pregunta cuidadosamente
- Selecciona la mejor respuesta para cada pregunta
- Puedes repetir el quiz tantas veces como quieras
- Tu progreso se mostrará en la parte superior