CodeGym /Cursos /C# SELF /record con cuerpo exp...

record con cuerpo explícito y record struct

C# SELF
Nivel 19 , Lección 3
Disponible

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);
Clase record posicional

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!";
    }
}
Record posicional con cuerpo y métodos extra

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);
Sintaxis posicional de record struct

¿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
Expresión with No
Inmutabilidad No (por defecto) Sí (init)
Comparación por valor No (por defecto)
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.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION