Articoli di programmazione

C# 9 Record contro Class e Struct...e due parole sui Value Types

C# 9 introduce il nuovo tipo record. Vediamo in modo rapido in cosa differisce da class e struct

music-1285165_1920.jpg
Record nasce per gestire in modo rapido ed efficiente i Value Types nelle nostre applicazioni.
Value Types, riferendoci a DDD, sono oggetti di dominio che non hanno identità, cioè non hanno ID: le loro identità sono definite dal loro stesso valore e sono spesso gestiti come immutabili, semplicemente perché è più comodo.
Ad esempio un indirizzo è un buon candidato ad esser trattato come Value Type.
La sua identità, è il suo stesso valore perché se il mio indirizzo è definito come "Via Roma 55, Livorno", nel mio sistema posso usarlo associato a me ed a qualsiasi altra classe Persona che abita con me e non ho bisogno di crearne uno per ogni mio convivente. Posso passarlo rapidamente ed efficientemente ad una classe Ordine come informazioni per la Spedizione.
Posso trattarlo come immutabile, cioè non consentirne la modifica perché se cambio casa, creo un nuovo indirizzo e me lo associo. Semplice.

Parentesi sulla persistenza nel database.
A livello di database questo oggetto Indirizzo, se non ci sono particolari esigenze, può essere memorizzato semplicemente nella tabella Persona, senza creare tabelle in relazione con essa.
In generale è bene tentare di persistere i Value Object nella stessa tabella dell'entità che li contiene, nel nostro caso la tabella Persona appunto. Se è una collection di Entity Objects (più indirizzi) è possibile serializzarla e salvarla in un solo capo che deserializzeremo al momento della lettura. Quando questo non è possibile, in base alle circostanze, perchè magari dobbiamo eseguire query su questa collection, possiamo predisporre una tabella relazionata e trattare esclusivamente a livello di database i Value Object come fossero Entity Objects.

C# 9, dicevamo, introduce il uovo tipo Record che può esser visto come una struct, con una marcia in più.
Come struct è un tipo valore.
Come struct l'uguaglianza è eseguita sui valori dei suoi campi.
Ma a differenza di struct è immutabile, cioè una volta creato non posso modificarlo.
Inoltre tramite la parola chiave with permette di creare nuovi record in modo estremamente rapido.
Altra cosa interessante, permette la "decostruzione" (deconstruction) in modo immediato senza bisogno di scrivere niente.
Vediamo con qualche test il comportamento di record, class e struct a confronto.
Prima creo i 3 tipi e subito noto che record si crea più rapidamente degli altri:

internal record Indirizzo(string Via, int Numero, string Città);

internal class IndirizzoClass
{
public IndirizzoClass(string via, int numero, string città)
{
Via = via;
Numero = numero;
Città = città;
}
public string Via;
public int Numero;
public string Città;
}

internal struct IndirizzoStruct
{
public IndirizzoStruct(string via, int numero, string città)
{
Via = via;
Numero = numero;
Città = città;
}
public string Via;
public int Numero;
public string Città;
}


Ora eseguo i test per mostrare i diversi comportamenti e tutti e 3 sono verdi:

[TestMethod]
public void RecordTest()
{
var i = new Indirizzo("Via Roma", 55, "Livorno");
var i2 = new Indirizzo("Via Roma", 55, "Livorno");
Assert.AreEqual(i, i2);
Assert.IsTrue(i == i2);
Assert.IsTrue(i.GetHashCode() == i2.GetHashCode());
}

[TestMethod]
public void ClassTest()
{
var i = new IndirizzoClass("Via Roma", 55, "Livorno");
var i2 = new IndirizzoClass("Via Roma", 55, "Livorno");
Assert.AreNotEqual(i, i2);
Assert.IsFalse(i == i2);
Assert.IsFalse(i.GetHashCode() == i2.GetHashCode());
Assert.IsFalse(ReferenceEquals(i, i2));
}

[TestMethod]
public void StructTest()
{
var i = new IndirizzoStruct("Via Roma", 55, "Livorno");
var i2 = new IndirizzoStruct("Via Roma", 55, "Livorno");
Assert.AreEqual(i, i2);
Assert.IsTrue(i.GetHashCode() == i2.GetHashCode());
}


Infine mostro una semplice decostruzione e l'uso di with:

[TestMethod]
public void RecordTest2()
{
var i = new Indirizzo("Via Roma", 55, "Livorno");
var i2 = i with { Città = "Firenze" };
(var via, var num, var cit) = i2;

Assert.AreEqual(via, "Via Roma");
Assert.AreEqual(num, 55);
Assert.AreEqual(cit, "Firenze");
}


Comodo vero?
#csharp #codice #ddd. #architettura