Expresión with: la mutación no destructiva crea un objeto nuevo con propiedades modificadas

Una expresión with genera una copia de su operando con las propiedades y campos especificados modificados. Se usa la sintaxis del inicializador de objeto para especificar qué miembros se van a modificar y sus nuevos valores:

using System;

public class WithExpressionBasicExample
{
    public record NamedPoint(string Name, int X, int Y);

    public static void Main()
    {
        var p1 = new NamedPoint("A", 0, 0);
        Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }
        
        var p2 = p1 with { Name = "B", X = 5 };
        Console.WriteLine($"{nameof(p2)}: {p2}");  // output: p2: NamedPoint { Name = B, X = 5, Y = 0 }
        
        var p3 = p1 with 
            { 
                Name = "C", 
                Y = 4 
            };
        Console.WriteLine($"{nameof(p3)}: {p3}");  // output: p3: NamedPoint { Name = C, X = 0, Y = 4 }

        Console.WriteLine($"{nameof(p1)}: {p1}");  // output: p1: NamedPoint { Name = A, X = 0, Y = 0 }

        var apples = new { Item = "Apples", Price = 1.19m };
        Console.WriteLine($"Original: {apples}");  // output: Original: { Item = Apples, Price = 1.19 }
        var saleApples = apples with { Price = 0.79m };
        Console.WriteLine($"Sale: {saleApples}");  // output: Sale: { Item = Apples, Price = 0.79 }
    }
}

El operando izquierdo de una expresión with puede ser de un tipo de registro . A partir de C# 10, un operando izquierdo de una expresión with también puede ser de un tipo de estructura o un tipo anónimo.

El resultado de una expresión with tiene el mismo tipo de entorno de ejecución que el operando de la expresión, como se muestra en el ejemplo siguiente:

using System;

public class InheritanceExample
{
    public record Point(int X, int Y);
    public record NamedPoint(string Name, int X, int Y) : Point(X, Y);

    public static void Main()
    {
        Point p1 = new NamedPoint("A", 0, 0);
        Point p2 = p1 with { X = 5, Y = 3 };
        Console.WriteLine(p2 is NamedPoint);  // output: True
        Console.WriteLine(p2);  // output: NamedPoint { X = 5, Y = 3, Name = A }
    }
}

En el caso de un miembro de tipo referencia, solo se copia la referencia a una instancia del miembro cuando se copia un operando. Tanto la copia como el operando original tienen acceso a la misma instancia de tipo de referencia. En el ejemplo siguiente se muestra ese comportamiento:

using System;
using System.Collections.Generic;

public class ExampleWithReferenceType
{
    public record TaggedNumber(int Number, List<string> Tags)
    {
        public string PrintTags() => string.Join(", ", Tags);
    }

    public static void Main()
    {
        var original = new TaggedNumber(1, new List<string> { "A", "B" });

        var copy = original with { Number = 2 };
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B

        original.Tags.Add("C");
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B, C
    }
}

Semántica de copia personalizada

Cualquier tipo de clase registro tiene el constructor de copia. Un constructor de copia es un constructor con un único parámetro del tipo de registro contenedor. Copia el estado de su argumento en una nueva instancia de registro. Al evaluar una expresión with, se llama al constructor de copia para crear instancias de una nueva instancia de registro en función de un registro original. Después, la nueva instancia se actualiza según las modificaciones especificadas. De forma predeterminada, el constructor de copia es implícito, es decir, lo genera el compilador. Si tiene que personalizar la semántica de la copia de registros, declare explícitamente un constructor de copia con el comportamiento deseado. En el ejemplo siguiente se actualiza el anterior con un constructor de copia explícito. El nuevo comportamiento de copia consiste en copiar los elementos de lista en lugar de una referencia de lista cuando se copia un registro:

using System;
using System.Collections.Generic;

public class UserDefinedCopyConstructorExample
{
    public record TaggedNumber(int Number, List<string> Tags)
    {
        protected TaggedNumber(TaggedNumber original)
        {
            Number = original.Number;
            Tags = new List<string>(original.Tags);
        }

        public string PrintTags() => string.Join(", ", Tags);
    }

    public static void Main()
    {
        var original = new TaggedNumber(1, new List<string> { "A", "B" });

        var copy = original with { Number = 2 };
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B

        original.Tags.Add("C");
        Console.WriteLine($"Tags of {nameof(copy)}: {copy.PrintTags()}");
        // output: Tags of copy: A, B
    }
}

No se puede personalizar la semántica de copia para los tipos de estructura.

Especificación del lenguaje C#

Para obtener más información, vea las secciones siguientes de la nota de propuestas de características de registros:

Consulte también