1. Introducción
La sintaxis posicional de una clase record es súper cómoda para casos sencillos:
public record User(string Name, int Age);
Pero a veces te entran ganas de añadirle métodos extra al record, propiedades no estándar, cambiar modificadores de acceso, meter lógica en el constructor (por ejemplo, validación o transformación automática de datos). ¡Pero en la declaración posicional no hay dónde meterlo! Es hora de pasar al cuerpo explícito si:
- Necesitas ampliar el record con métodos, propiedades o lógica adicional.
- Quieres controlar el comportamiento de las propiedades (setters, getters, inits, validación).
- Te hace falta crear varios constructores diferentes para distintas formas de inicialización.
- Quieres añadir interfaces o implementar métodos especiales.
Construyendo un record con cuerpo
La sintaxis es muy parecida a una clase. Seguro que te suena esto:
public class User
{
/* ... */
}
Pero ahora es un record:
public record User
{
// Propiedades definidas explícitamente
public string Name { get; init; }
public int Age { get; init; }
// Lógica adicional
public string GetGreeting()
{
return $"¡Hola, me llamo {Name} y tengo {Age} años!";
}
// Constructor personalizado
public User(string name, int age)
{
Name = name;
if (age < 0)
throw new ArgumentException("¡Age no puede ser negativo!");
Age = age;
}
}
Dato curioso: Si defines tus propiedades explícitamente, el compilador no crea automáticamente propiedades desde la sintaxis posicional. Todo explícito, todo bajo control.
Variante mixta: sintaxis híbrida
C# te deja mezclar ambos mundos: puedes declarar un record posicional y añadirle cuerpo:
public record User(string Name, int Age)
{
public string GetGreeting()
{
return $"Soy {Name}, tengo {Age} años!";
}
}
En este caso, las propiedades Name y Age se siguen creando automáticamente con la sintaxis posicional, y tus métodos extra se quedan dentro del cuerpo.
2. Diferencias con los records normales — matices del cuerpo
- Un record explícito te deja controlar totalmente constructores, propiedades y métodos.
- Puedes implementar interfaces o meter tu propia lógica de comparación si tienes reglas de identificación de objetos más complejas.
- A diferencia de un record posicional simple, para añadir nuevas propiedades solo tienes que declararlas en el cuerpo del record.
// ¡Error de novato!
public record User(string Name, int Age)
{
public string Name { get; init; } // ← ¡conflicto! Declaración duplicada de propiedad
}
Errores de novato: Algunos estudiantes intentan escribir public record User(string Name, int Age) y además meter en el cuerpo la propiedad public string Name { get; init; }, pensando que son variables distintas. ¡No! Eso es un conflicto (declaración duplicada). O usas solo la sintaxis posicional, o defines las propiedades explícitamente — no mezcles.
Ejemplos prácticos
Seguimos desarrollando una app de consola donde el usuario crea pedidos. Imagina que la clase de pedido Order hasta ahora era así:
public record Order(string Product, int Quantity, double Price);
Ahora necesitamos validar la cantidad (quantity no puede ser menor que 1) y una propiedad extra que calcule el coste total:
public record Order
{
public string Product { get; init; }
public int Quantity { get; init; }
public double Price { get; init; }
public double TotalCost => Quantity * Price;
public Order(string product, int quantity, double price)
{
Product = product ?? throw new ArgumentNullException(nameof(product));
if (quantity < 1)
throw new ArgumentException("¡Cantidad debe ser al menos 1!");
Quantity = quantity;
Price = price;
}
public override string ToString()
=> $"Producto: {Product}, Cantidad: {Quantity}, Total: {TotalCost}";
}
Fíjate que hemos definido las propiedades explícitamente, las hemos hecho con init-setter (objetos inmutables), y hemos añadido el cálculo automático del coste — ¡el código es mucho más flexible!
Llamada en el programa:
var order = new Order("Bicicleta", 2, 15000);
Console.WriteLine(order); // Producto: Bicicleta, Cantidad: 2, Total: 30000
3. record struct
Evolución de los struct
Antes de que existiera record struct, las struct en C# eran "currantes sencillos" — se copian rápido, viven en la pila, perfectos para pequeños “Parcel” de datos (tipo coordenadas o colores). Pero no tenían todas las ventajas de los records: no había sintaxis posicional, ni with-expresiones, ni comparación por valor por defecto ni otras “chuches”.
Ahora en C# puedes declarar structs al estilo record:
public record struct Point(int X, int Y);
¿Y esto qué aporta?
- Implementación automática de Equals, GetHashCode, ToString — ¡tu struct ya sabe compararse y mostrarse bonito!
- Sintaxis de clonación with: var p2 = p1 with { X = 10 };
- Puedes usar sintaxis posicional o explícita.
Comparativa: struct clásico VS record struct
| struct | record struct | |
|---|---|---|
| Sintaxis posicional | No | Sí |
| Expresión with | No | Sí |
| Inmutabilidad | No (por defecto) | Sí (init) |
| Comparación por valor | No (por defecto) | Sí |
| ToString | Estándar | Bonito |
Sintaxis explícita de cuerpo para record struct
Todo igual que en un record normal, solo que es struct:
public record struct Rectangle
{
public int Width { get; init; }
public int Height { get; init; }
public int Area => Width * Height;
public Rectangle(int width, int height)
{
Width = width > 0 ? width : throw new ArgumentException("Ancho > 0");
Height = height > 0 ? height : throw new ArgumentException("Alto > 0");
}
public void Print()
{
Console.WriteLine($"Tamaños: {Width} x {Height}, área: {Area}");
}
}
Ejemplo de uso:
var rect = new Rectangle(10, 7);
rect.Print(); // Tamaños: 10 x 7, área: 70
// Clonamos y cambiamos el ancho, sin tocar el original
var wideRect = rect with { Width = 20 };
wideRect.Print(); // Tamaños: 20 x 7, área: 140
Características de record struct
- Sigue siendo un struct — value type. ¡Se copia al asignar!
- Todas las ventajas de los records: comparación por valor, clonación con with, ToString bonito.
- Recomendado para conjuntos de datos pequeños, compactos e inmutables, donde quieres evitar asignaciones en el heap.
- Puedes declarar parámetros posicionales o “abrir” el cuerpo explícitamente.
4. Errores típicos y trampas
Demasiada mutabilidad: record struct no hace los campos inmutables automáticamente si los declaras normales (por ejemplo, public int Value;). ¡Usa setters init para un struct realmente inmutable!
Comparación: Si añades campos nuevos a mano (sin meterlos en la sintaxis posicional), ojo: solo los campos del constructor o con setter init participan en la comparación automática por valor.
Copiado: Esto es un struct, así que... ¡todo se copia! No lo confundas con records de referencia.
Líos con with-expresiones: Siempre hacen shallow copy, o sea, NO copian profundamente los objetos anidados.
GO TO FULL VERSION