Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

18 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 28 de mayo de 2024
C#

Me da la impresión de que el operador with de C# no es demasiado conocido, y creo que vale la pena echarle un vistazo porque puede resultarnos útil en nuestro día a día.

Introducido con C# 9, allá por 2020, las expresiones with proporcionan una forma sencilla de crear nuevas instancias de objetos "copiando" las propiedades de otro objeto, pero con la posibilidad de modificar algunas de ellas sobre la marcha. Esto, aunque puede usarse con varios tipos de objeto, es especialmente interesante cuando estamos trabajando con componentes inmutables, como son los records, porque la única posibilidad que tenemos de alterarlos es creando copias con las modificaciones que necesitemos.

Para verlo claro, fijaos primero en el siguiente ejemplo, donde creamos un record para almacenar datos de un amigo, y luego mostramos los datos por consola:

// Los tipos Friend y Address son definidos más abajo
var john = new Friend("John", 30, new Address("Madrid", "Spain"));
WriteFriend(john); // John is 30 years old and lives in Madrid (Spain)

void WriteFriend(Friend friend)
{
    Console.WriteLine(
        $"{friend.Name} is {friend.Age} years old and lives in " +
        $"{friend.Address.City} ({friend.Address.Country})"
    );
}

record Friend(string Name, int Age, Address Address);
record Address(string City, string Country);

Hasta aquí, todo bien. Pero ahora, si intentamos modificar la edad de John, el sistema lanzará un error en compilación, porque los records son por definición inmutables:

john.Age = 31; // CS8852: Friend.Age can not be assigned

Entonces, la única forma que tendríamos para hacerlo sería copiar el objeto john con la edad modificada, y para ello, usando C# "clásico", tendríamos que mapear todas las propiedades del objeto original a uno nuevo, modificando la que nos interesa:

var john = new Friend("John", 30, new Address("Madrid", "Spain"));
WriteFriend(john); // John is 30 years old and lives in Madrid (Spain)

var olderJohn = new Friend(john.Name, john.Age+1, 
    new Address(john.Address.City, john.Address.Country)
);
WriteFriend(olderJohn); // John is 31 years old and lives in Madrid (Spain)

Esto, aunque efectivo, es tedioso y propenso a errores. Aquí es donde entra en juego el operador with, o las with expressions. Este operador nos permite "clonar" o crear nuevas instancias de objetos con las propiedades de otro de forma automática, pero añadiendo la posibilidad de introducir modificaciones de los valores que nos interesen. Por ejemplo, el caso anterior podríamos simplificarlo de la siguiente manera:

var john = new Friend("John", 30, new Address("Madrid", "Spain"));
WriteFriend(john); // John is 30 years old and lives in Madrid (Spain)

var olderJohn = john with { Age = john.Age + 1 };
WriteFriend(olderJohn); // John is 31 years old and lives in Madrid (Spain)

Mucho mejor así, ¿verdad? 😉 Fijaos la facilidad con que se especifican los nuevos valores, incluso con distintos niveles de anidamiento:

var john = new Friend("John", 30, new Address("Madrid", "Spain"));
WriteFriend(john); // John is 30 years old and lives in Madrid (Spain)

var anotherJohn = john with { 
    Age = john.Age + 1, 
    Address = john.Address with { City = "Jaen" } 
};
WriteFriend(anotherJohn); // John is 31 years old and lives in Jaen (Spain)

Pero bueno, comentábamos antes que with no es exclusivo de records, sino que desde C# 10 puede usarse con otros tipos de objetos, como las estructuras. En el siguiente ejemplo podemos observarlo:

var x = new Point() { X = 1, Y = 2 };
var y = x with { X = 3 };

Console.WriteLine($"x: {x.X}, {x.Y}"); // x: 1, 2
Console.WriteLine($"y: {y.X}, {y.Y}"); // y: 3, 2

struct Point
{
    public int X;
    public int Y;
}

Otra posibilidad, introducida también en C# 10, es que el operador with puede usarse con tipos anónimos, y es muy interesante porque, como ocurre con los records los objetos anónimos son también son inmutables.

var friend = new { Name = "John", Age = 30 };
Console.WriteLine($"{friend.Name} is {friend.Age}"); // John is 30
friend.Age++; // Error CS0200: Cannot be assigned to - it is read only

De nuevo, es un escenario donde podremos usar with para realizar una copia del objeto anónimo con las modificaciones que necesitemos:

var friend = new { Name = "John", Age = 30 };
var olderFriend = friend with { Age = friend.Age + 1 };
Console.WriteLine($"{olderFriend.Name} is {olderFriend.Age}"); // John is 31

¡Espero que os sea útil!

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!