.Net core 5.0 lega la sua uscita a C# 9. Come tutti gli aggiornamenti che il linguaggio porta con se, troviamo una serie di nuove funzionalità ed arricchimenti.
Di seguito le nuove implementazioni, dettagliate all’interno delle relative sezioni:
- Tipo Record e Init only setter
- Migliormamenti nell’uso dei discard
- Top level statement
- Target-typed new expression
- Covariant returns
- Miglioramenti Pattern Matching
Tipo Record e init only setter
Al momento della definizione di una classe, utilizzata ad esempio per la definzione di DTO, è possibile utilizzare il tipo record. Vediamo come funziona e che differenze ci sono con il tipo class.
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}Quello che avviene dietro alle quinte, in fase di compilazione, è che il tipo record viene trasformato in una class.
Durante la trasformazione quello che si porta dietro in maniera automatica:
- l’implementazione di Equals.
- l’interfaccia IEquatable
- l’implementazione del metodo GetHashCode
- l’implementazione del metodo ToString
- l’implementazione di Clone
Un oggetto immutabile è un oggetto che una volta creato non può più cambiare. Il vantaggio di utilizzare un oggetto immutabile è che risulta thread-safe e non interessato da race-condition.
Fino a C# 9, C# non supportava l’immutabilità in maniera semplice. Con C# 9 è possibile gestire l’immutiablità utilizzando init-only settings e il tipo record. In particolare init-only consente di rendere una singola proprietà immutabile, mentre il tipo record per rendere immutabile l’intero oggetto.
Poichè l’immutabilità garantisce che un oggetto non cambierà nel tempo, è una delle proprietà desiderae all’interno di applicazioni multi-thread e per la rappresentazione di DTO.
Per poter utilizzare proprietà immutabili è sufficiente definirne il setter come init:
public class Person
{
public string DbName { get; init; }
public string DbType { get; init; }
}è possibile creare un’istanza della classe nel modo tradizionale:
Person dbMetadata = new Person()
{
DbName = "Test",
DbType = "Oracle"
};ma se nel codice ci fossere ulteriori assegnazioni, del tipo :
dbMetadata.DbType = "SQL Server";
verrebbe generato un errore in fase di compilazione.
Passando al tipo record, lo possiamo definire come un tipo immutabile (per intenderci potremmo definirlo anche come classe light) che ha tutte le proprietà read-only.
L’inizializzazione delle sue proprietà può avvenire solo mediante il suo costruttore. Ritornando ad una classe Person:
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
public string Address { get; init; }
public string City { get; init; }
public string Country { get; init; }
}possiamo inizializzare le sue proprietà nel modo seguente:
var person = new Person
{
FirstName = "Joydip",
LastName = "Kanjilal",
Address = "192/79 Stafford Hills",
City = "Hyderabad",
Country = "India"
};With-expression
Supponiamo di voler creare un oggetto a partire da un altro oggetto, impostando nella pratica, tutti i valori delle stesse proprietà. Utilizzando init come setter, tutto questo non è possibile:
var newPerson = person; newPerson.Address = "112 Stafford Hills"; newPerson.City = "Bangalore";
e genera un errore di compilazione. Per poter ovviare a questa situazione esiste però un workaround, ovvero la parole chiave with. In pratica è possibile creare un nuovo oggetto a partire da quello sorgente, indicando quali sono le proprietà che possono essere modificabili.
var newPerson = person with
{ Address = "112 Stafford Hills", City = "Bangalore" };Nell’esempio precedente stiamo creando un nuovo oggetto newPerson (di tipo Person) e stiamo però rendendo modificabili le proprietà Address e City, utilizzando la keyword with.
Ereditarietà con i Record
Il tipo Record supporta l’ereditarietà. E’ possibile ereditare da tipo Record ed aggiungere nuove proprietà .
Verifica di Uguaglianza tra Record
Quando effettuiamo il controllo di uguaglianza tra due classi in C#, la comparazione viene effettuata by reference. Quanto effettuaiamo la comparazione tra due istanze di record, la comparazione avviene per valore.
Supponiamo di aver definito il seguente record:
public record TipoDato
{
public string Name { get; init; }
public string Type { get; init; }
}e creiamo due istanze:
TipoDato data1 = new TipoDato()
{
Name = "Test",
Type = "Oracle"
};
TipoDato data2 = new TipoDato()
{
Name = "Test",
Type = "SQL Server"
};A questo punto è possibile eseguire la comparazione utilizzando il metodo Equals:
Console.WriteLine(data1.Equals(data2)); Console.WriteLine(data2.Equals(data1));
eseguendo la comparazione per valore, entrambe le comparazioni restituisco false.
Miglioramenti nell’uso dei discard
A partire da C# 9.0, è possibile usare discards per specificare due o più parametri di input di un’espressione lambda che non vengono usati nell’espressione:
Func<int, int, int> constant = (_, _) => 42;
come nell’esempio precedente. Utilizzare i discard come parametri può essere utile quando si usa una funzione lambda per la gestione di eventi.