Expresión with (referencia de C#)
Disponible en C# 9.0 y versiones posteriores, se trata de una expresión with que genera una copia de su operando con las propiedades y los campos especificados modificados:
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.19" };
Console.WriteLine($"original apples: {apples}");
var saleApples = apples with { Price = "0.79" };
Console.WriteLine($"sale apples: {saleApples}");
}
}
Como se muestra en el ejemplo anterior, se usa la sintaxis de inicializador de objeto para especificar qué miembros se van a modificar y sus nuevos valores.
En C# 9.0, un operando izquierdo de una expresión with debe 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. 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: