
Nel mio precedente articolo (parte prima e parte seconda), abbiamo visto le novità introdotte nella versione 8 di C#, ma il team sta già lavorando alla versione 9, di cui possiamo apprezzare alcune novità.
Ovviamente, al momento le nuove funzionalità sono ancora in fase di sviluppo e fino al rilascio definitivo potrebbero esserci variazioni a quello che c’è attualmente. I più curiosi, però apprezzeranno uno sguardo al lavoro che il team di Microsoft sta facendo.
Simplified Parameter NULL Validation Code
Una delle novità al vaglio del team semplifica il controllo sui valori null abbreviando la sintassi attraverso l’utilizzo di una annotazione “!” sul nome del parametro di un metodo:
// Before:
void Insert(string s)
{
if (s is null)
throw new ArgumentNullException(nameof(s));
}
// After:
void Insert(string s!)
{
}
Primary Constructors
Il Primary Constructor ha lo scopo di semplificare la dichiarazione delle proprietà e dei costruttori di una classe:
// Before:
class Person
{
private string _name;
public Person(string name)
{
_name = name;
}
public string Name
{
get => _name;
set {
if (value == null)
{
throw new NullArgumentException(nameof(Name));
}
_name = value;
}
}
}
// After:
class Person(string name)
{
public string Name
{
get => name;
set
{
if (value == null)
{
throw new NullArgumentException(nameof(Name));
}
name = value;
}
}
}
Record
Il Team vorrebbe che il linguaggio fornisse un modo per impostare un membro di sola lettura negli inizializzatori di oggetti. È stato quindi introdotto un nuovo modificatore initonly da applicare alle proprietà e ai campi.
Un Record può contenere operatori, metodi e proprietà. Queste ultime sono, per default, di sola lettura (Immutable Type). I Record possono essere di tipo valore o tipo riferimento e ci consentono di confrontare l’uguaglianza strutturale. I Record sono utili per rappresentare un dato complesso con molte proprietà, come un record di database o un DTO. Vediamo un esempio:
data class UserInfo
{
public string Username { get; }
public string Email { get; }
public bool IsAdmin { get; } = false;
}
Quando si lavora con tipi immutabili, in genere, si apportano modifiche a un oggetto costruendo una copia modificata, anziché apportare le modifiche direttamente sull’oggetto. Con la nuova sintassi si potrebbe invece optare per qualcosa del genere:
var userInfo = new UserInfo()
{
Username = "Francesco",
Email = "francesco@microsoft.com",
IsAdmin = true
};
var newUserName = userInfo with { Username = "Vas" };
L’oggetto newUserName risultante sarebbe una copia di userInfo, ma con la proprietà Username con valore = “Vas”. Come abbiamo detto l’uguaglianza tra Record viene comparata per struttura e non per referenza:
var userInfo1 = new UserInfo() {
Username = "Francesco",
Email = "francesco@microsoft.com",
IsAdmin = true
};
var userInfo2 = new UserInfo()
{
Username = "Francesco",
Email = "francesco@microsoft.com",
IsAdmin = true
};
var compareUsersInfo = userInfo1 == userInfo2; // true
Enum class
Collegata ai Record visti in precedenza, una enum class potrebbe essere un nuovo modo di scrivere una classe astratta e le classi concrete che la implementano. Se ci sono più definizioni parziali della enum class, tutti i membri saranno considerati membri della definizione della classe enum. Diversamente da una definizione di classe astratta definita dall’utente, il tipo root della classe enum è parziale per impostazione predefinita ed è strutturato in modo da avere solamente un costruttore privato predefinito senza parametri. Non sono consentiti costruttori definiti dall’utente.
Inoltre, nessun tipo può ereditare direttamente da essa in nessuna dichiarazione, a parte quelli specificati come membri della enum class stessa.
// If we had this situation:
public partial abstract class Shape { }
public class Rectangle : Shape {
public double Width { get; }
public double Length { get; }
public Rectangle(double Width, double Length){
this.Width = Width;
this.Length = Length;
}
}
public class Circle : Shape {
public double Radius { get; }
public Circle(double Radius)
{
this.Radius = Radius;
}
}
// It could be:
enum class Shape
{
Rectangle(double Width, double Length);
Circle(double Radius);
}
Le classi enum possono anche contenere value member, che definiscono le proprietà statiche pubbliche di tipo get-only sul tipo root e restituiscono il tipo root stesso:
enum class Color
{
Red, Green
}
Che è equivalente a:
partial abstract class Color
{
public static Color Red => ...;
public static Color Green => ...;
}
Discriminated Unions
Simile alle discriminated union in F# (un linguaggio ibrido funzionale /Object-Oriented appartenente all’ecosistema .NET), questa feature è collegata a quelle precedenti e ci offre un modo per definire e utilizzare tipi di dato che possono contenere qualsiasi numero di tipi diversi tra loro.
Facendo un mix delle funzionalità viste in precedenza, prendiamo in esame il seguente codice:
public class Person // Define a record type
{
public initonly string Firstname { get; }
public initonly string Lastname { get; }
};
enum class IntOrBool { int I; bool B; } // Define an enum class
enum class MixedType // Define an enum class
{
Person P;
IntOrBool Union;
Costruiamo ora le istanze del Record e della Union:
var person = new Person()
{
Firstname = “Francesco”;
Lastname = “Vas”;
};
var unionRecord = new MixedType.P(person); // Record C# 9
var unionTypeInt = new MixedType.Union(I 86); // Int type
var unionTypeBool = new MixedType.Union(B false); // Boolean type
Potremmo quindi avere una funzione che ci restituisca uno dei tre tipi appartenenti al root type MixedType (ossia Person, int, bool) su cui poter utilizzare il pattern matching.
A scopo dimostrativo, vediamo ora qualche altro esempio pratico di possibile utilizzo.
Gestione delle eccezioni:
try { ... }
catch (CommunicationException|SystemException ex) {
// Handle the exception
}
Pattern matching:
if (result is short|int|long number) { ... }
Type constraint:
public class GenericClass<T> where T : T1 | T2 | T3
Typed heterogeneous collections:
var inputs = new List<int|double|string>{3, 9.9, "abc"};
And, or, and not patterns
L’ultima feature che andremo ad esaminare è la possibile introduzione di tre nuove modalità di valutazione di un case di switch:
switch (obj)
{
case 1 or 2:
case Point(0, 0) or null:
case Point(var x, var y) and var p:
}
Conclusioni
Direi di fermarci qui, visto che il tutto è ancora da definire e gli sviluppatori stanno lavorando duro per semplificarci il lavoro ed introdurre nuove funzionalità. Spero di aver stuzzicato la vostra curiosità, e se siete interessati, vi rimando al repository di GitHub che contiene tutte le proposte e le discussioni al vaglio del team Microsoft (Dio salvi l’open-source!):
https://github.com/dotnet/csharplang/milestone/15
Al prossimo articolo! Stay Tuned!