0Pricing
C# Academy · Lección

Inmutabilidad con init y with

Use setters que solo acepten init para crear objetos inmutables y expresiones with para producir copias modificadas de forma no destructiva.

Inmutabilidad con init y with es una lección gratuita de C# Academy en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de C# Academy, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de C# Academy incluye 4 lecciones en total.

¿Qué es la inmutabilidad?

Un objeto inmutable no puede modificarse después de su creación. La inmutabilidad evita mutaciones accidentales, hace que los objetos sean seguros para subprocesos de forma predeterminada y simplifica el razonamiento sobre el código. C# proporciona los establecedores init y las expresiones with para facilitar la inmutabilidad.

Establecedores init-only

Un descriptor de acceso init permite establecer una propiedad durante la inicialización del objeto (en el constructor o en un inicializador de objeto), pero nunca después. Es la alternativa inmutable a set.

public class Point
{
    public double X { get; init; }
    public double Y { get; init; }
}

var p = new Point { X = 3.0, Y = 4.0 }; // OK — init phase
p.X = 5.0; // COMPILE ERROR — cannot set after init

// All of these are valid init-phase assignments:
var p2 = new Point(X: 1.0, Y: 2.0);
var p3 = new Point { X = 0, Y = 0 };

init en propiedades posicionales de records

Las propiedades posicionales de los records son de tipo init de forma predeterminada; eso es lo que hace que los records sean inmutables. El compilador genera get; init; para cada parámetro posicional.

// This record:
public record Person(string Name, int Age);

// Is equivalent to:
public record Person
{
    public string Name { get; init; }
    public int Age    { get; init; }
    public Person(string Name, int Age) { this.Name = Name; this.Age = Age; }
    // + Deconstruct, Equals, GetHashCode, ToString
}

var alice = new Person("Alice", 30);
alice.Name = "Bob"; // COMPILE ERROR

Expresiones with: mutación no destructiva

Una expresión with crea una copia de un record con determinadas propiedades modificadas. El original no cambia: es verdadera inmutabilidad.

public record Person(string Name, int Age, string Email);

var alice = new Person("Alice", 30, "alice@example.com");

// Create a modified copy — alice is unchanged
var olderAlice = alice with { Age = 31 };
var renamed    = alice with { Name = "Alicia", Email = "alicia@example.com" };

Console.WriteLine(alice.Name);      // Alice (unchanged)
Console.WriteLine(olderAlice.Age);  // 31
Console.WriteLine(renamed.Name);    // Alicia

with en tipos que no son records

C# 10 permite usar with en cualquier struct o clase que tenga semántica de constructor de copia, aunque resulta más natural con records. En las clases, debe implementarla manualmente.

// struct with with-expression:
public struct Temperature
{
    public double Celsius { get; init; }
    public double Fahrenheit => Celsius * 9 / 5 + 32;
}

var t1 = new Temperature { Celsius = 20 };
var t2 = t1 with { Celsius = 25 }; // copy with change
Console.WriteLine(t1.Celsius); // 20 — unchanged
Console.WriteLine(t2.Celsius); // 25

Encadenamiento de expresiones with

Encadene varias expresiones with para crear transformaciones complejas; cada paso crea un nuevo valor inmutable a partir del anterior.

public record Order(int Id, string Status, decimal Total, DateTime UpdatedAt);

var order = new Order(42, "Pending", 99.99m, DateTime.UtcNow);

// Apply a promotion discount then mark as confirmed
var finalOrder = order
    with { Total    = order.Total * 0.9m }    // 10% off
    with { Status   = "Confirmed" }
    with { UpdatedAt = DateTime.UtcNow };

Console.WriteLine(order.Status);     // Pending (original unchanged)
Console.WriteLine(finalOrder.Status); // Confirmed

Propiedades calculadas en tipos inmutables

Las propiedades derivadas de tipos inmutables son naturalmente puras: se calculan a partir de valores de propiedades fijos y siempre devuelven el mismo resultado para la misma entrada.

public record Money(decimal Amount, string Currency)
{
    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Currency mismatch");
        return this with { Amount = Amount + other.Amount };
    }

    public Money Multiply(decimal factor) =>
        this with { Amount = Amount * factor };

    public override string ToString() =>
        $"{Amount:F2} {Currency}";
}

var price = new Money(10.00m, "USD");
var tax   = price.Multiply(0.08m);
var total = price.Add(tax);
Console.WriteLine(total); // 10.80 USD

Colecciones inmutables

Combine records con ImmutableList<T> y otros tipos de System.Collections.Immutable para crear estructuras de datos completamente inmutables.

using System.Collections.Immutable;

public record ShoppingCart(
    string UserId,
    ImmutableList<CartItem> Items)
{
    public ShoppingCart AddItem(CartItem item) =>
        this with { Items = Items.Add(item) };

    public ShoppingCart RemoveItem(int productId) =>
        this with { Items = Items.RemoveAll(i => i.ProductId == productId) };

    public decimal Total => Items.Sum(i => i.Price * i.Quantity);
}

Seguridad para subprocesos gracias a la inmutabilidad

Los objetos inmutables son intrínsecamente seguros para subprocesos: no necesitan sincronización al compartirlos entre subprocesos, porque su estado no puede cambiar después de su construcción.

// Immutable config record shared across all threads safely
public record AppConfig(
    string ConnectionString,
    int MaxRetries,
    TimeSpan Timeout);

// Register as singleton — safe because record is immutable
builder.Services.AddSingleton(
    new AppConfig(
        ConnectionString: config["DB"]!,
        MaxRetries: 3,
        Timeout: TimeSpan.FromSeconds(30)));

// Any thread can read this simultaneously without locks

Caso real: abastecimiento funcional de eventos

Los records inmutables encajan de forma natural con el abastecimiento de eventos: cada evento de dominio es inmutable y el estado se deriva plegando los eventos; no hay mutaciones ni sorpresas.

public record OrderState(
    int Id,
    string Status = "Draft",
    decimal Total = 0m);

public static OrderState Apply(OrderState state, object evt) => evt switch
{
    OrderPlaced   e => state with { Status = "Pending", Total = e.Total },
    OrderShipped  _ => state with { Status = "Shipped" },
    OrderCancelled _ => state with { Status = "Cancelled" },
    _ => state
};

// Fold events to get current state:
var state = events.Aggregate(
    new OrderState(id),
    Apply);

Comprobación rápida

¿Qué le ocurre al record original cuando se usa una expresión 'with'?

Resumen: inmutabilidad con init y with

Conceptos clave:

  • Descriptor de acceso init: solo se puede establecer durante la inicialización, nunca después
  • Las propiedades posicionales de los records son de tipo init de forma predeterminada
  • Expresión with: crea una copia modificada; el original no cambia
  • Encadene expresiones with para realizar transformaciones en varios pasos
  • Los tipos inmutables son seguros para subprocesos sin sincronización
  • Combínelos con ImmutableList<T> para crear grafos de objetos completamente inmutables

Preguntas frecuentes

¿La lección «Inmutabilidad con init y with» es gratis?

Sí — el texto completo de «Inmutabilidad con init y with» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de C# Academy, actualiza a CoddyKit PRO. El curso de C# Academy incluye 4 lecciones en total.

¿Qué aprenderé en «Inmutabilidad con init y with»?

Use setters que solo acepten init para crear objetos inmutables y expresiones with para producir copias modificadas de forma no destructiva. Practicas C# Academy con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar C# Academy?

No se requiere experiencia previa. C# Academy en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Inmutabilidad con init y with»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de C# Academy?

Sí. Cada lección de C# Academy incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Tipos record: fundamentos y sintaxis
  2. Inmutabilidad con init y with
  3. Igualdad de valores y deconstrucción
  4. Records en el diseño guiado por el dominio
← Volver a C# Academy