C# 9.0
Propiedades solo de init
Las propiedades tienen que ser mutables para que los inicializadores de objeto funcionen: primero se llama al constructor del objeto y luego se le asigna valor.
Las propiedades de solo init permiten introducir un descriptor que es una variable que se puede llamar durante la inicialización del objeto: initset.
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Descriptores de acceso Init y campos readonly
Los descriptores de acceso sólo pueden ser llamados durante la inicialización, pero se les permite mutar campos de la clase envolvente initreadonly.
public class Person
{
private readonly string firstName;
private readonly string lastName;
public string FirstName
{
get => firstName;
init => firstName = (value ?? throw new ArgumentNullException(nameof(FirstName)));
}
public string LastName
{
get => lastName;
init => lastName = (value ?? throw new ArgumentNullException(nameof(LastName)));
}
}
Registros
La palabra clave data en la declaración de la clase marca dicha entidad como registro. Los registros están destinados a ser valores y no objetos. Están destinados a un estado no mutable. No se define por su identidad, sino por su contenido.
public data class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
Con expresiones
Una posibilidad de trabajar con entidades no mutables consiste en crear nuevas entidades a partir de otras entidades no mutables. Si copiáramos una entidad no mutable y le cambiásemos el valor de la propiedad inicializadora, estaríamos haciendo una mutación no destructiva.
var otherPerson = person with { LastName = "Hanselman" };
Un registro define implícitamente un constructor de copia, lo que hace es tomar un objeto de otro existente.
protected Person(Person original) { /* copy all the fields */ } // generated
Igualdad basada en valor
Todos los objetos heredan un método virtual de la clase. Esto se utiliza como base para el método estático cuando ambos parámetros no son null.Equals(object)objectObject.Equals(object, object)
Structs invalida esto para tener "igualdad basada en valores", comparando cada campo de la estructura llamando a ellos recursivamente. Los registros hacen lo mismo.Equals
Esto significa que, de acuerdo con su "valor", dos objetos de registro pueden ser iguales entre sí sin ser el mismo objeto. Por ejemplo, si modificamos el apellido de la persona modificada de nuevo:
var originalPerson = otherPerson with { LastName = "Hunter" };
Ahora tendríamos el valor de "falso" (no son el mismo objeto), pero de verdad (tienen el mismo valor).ReferenceEquals(person, originalPerson)Equals(person, originalPerson)
Registros opcionales
Es posible indicar el constructor y deconstructor de un registro:
public data class Person
{
string FirstName;
string LastName;
public Person(string firstName, string lastName)
=> (FirstName, LastName) = (firstName, lastName);
public void Deconstruct(out string firstName, out string lastName)
=> (firstName, lastName) = (FirstName, LastName);
}
Hay una sintaxis más abreviada, es la siguiente:
public data class Person(string FirstName, string LastName);
Esto declara las propiedades automáticas de solo inicio público y el constructor y el deconstructor, para que pueda escribir:
var person = new Person("Scott", "Hunter"); // positional construction
var (f, l) = person; // positional deconstruction
Con expresiones y herencia
La igualdad basada en el valor y la mutación no destructiva son notoriamente desafiantes cuando se combinan con herencia.
public data class Person { string FirstName; string LastName; }
public data class Student : Person { int ID; }
Y vamos a empezar nuestro ejemplo -expression creando realmente un , pero almacenándolo en una variable
Person person = new Student { FirstName = "Scott", LastName = "Hunter", ID = GetNewId() };
otherPerson = person with { LastName = "Hanselman" };
Igualdad y herencia basadas en valor
De forma similar a la compatibilidad con -expression, la igualdad basada en valores también tiene que ser "virtual", en el sentido de que s necesita comparar todos los campos, incluso si el tipo conocido estáticamente en el punto de comparación es un tipo base. Esto se logra fácilmente reemplazando el método ya virtual.
A la hora de comparar C# se encarga automáticamente. La forma en que se hace es que los registros tienen una propiedad protegida virtual llamada EqualityContract. Cada registro derivado lo reemplaza y, para compararlo, los dos objetos deben tener el mismo.
Mejora de la coincidencia de patrones
Se han añadido varios tipos nuevos de patrones en C- 9.0:
- Patrones de tipo simple:
DeliveryTruck => 10.00m,
- Patrones relacionales: como una expresión de conmutador anidada
DeliveryTruck t when t.GrossWeightClass switch
{
> 5000 => 10.00m + 5.00m,
< 3000 => 10.00m - 2.00m,
_ => 10.00m,
}
- Patrones lógicos: puede combinar patrones con operadores lógicos y , escritos como palabras para evitar confusiones con los operadores utilizados en expresiones
DeliveryTruck t when t.GrossWeightClass switch
{
< 3000 => 10.00m - 2.00m,
>= 3000 and <= 5000 => 10.00m,
> 5000 => 10.00m + 5.00m,
},
Podemos dividir el manejo de casos desconocidos dependiendo de si son nulos
not null => throw new ArgumentException($"Not a known vehicle type: {vehicle}", nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
También not va a ser conveniente en condiciones que contienen es-expresiones donde, en lugar de paréntesis dobles difíciles de manejar
if (!(e is Customer)) { ... }
if (e is not Customer) { ... }
Mecanografía de objetivo mejorada
Expresiones que no tenían escritos de destino anteriormente pueden guiarse por su contexto.
- Expresiones new con tipo destino
Point p = new (3, 5);
- Tipos ?? y ?: cuando no comparten tipo actualmente falla, ahora no:
Person person = student ?? customer; // Shared base type int? result = b ? 0 : null; // nullable value type
- Covariante de retorno: a veces es útil expresar que un método override en una clase derivada tiene un tipo de retorno más específico que la declaración en el tipo base.
abstract class Animal
{
public abstract Food GetFood();
...
}
class Tiger : Animal
{
public override Meat GetFood() => ...;
}