C# v.9.0

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() => ...;
}