SEALED ToString() IN C# 10

L’argomento riguarda la possibilità di impedire l’override del metodo ToString() della classe Object all’interno della gerarchia dei record Type.

public virtual string? ToString();

Tale metodo della classe Object viene ereditato da tutti i tipi e rappresenta un’informazione testuale sull’oggetto su cui viene invocato il metodo. Se non ridefiniamo opportunamente il metodo ToString() esso ritorna il nome del Type su cui è stato invocato. Nelle precedenti lezioni quando abbiamo parlato dei record abbiamo visto che il metodo ToString() è stato ridefinito dal compilatore per avere le seguenti informazioni sul record.

Libro {Titolo=” L’isola misteriosa”, Autore=” Jules Verne”}

using System;
    Libro libro1 = new Libro("L'isola misteriosa","Jules Verne");
    Console.WriteLine(libro1);//LIBRO
    record Libro
    {
        #nullable enable
        public string Titolo {get;init;}
        public string Autore {get;init;}
        
        #nullable disable
        public Libro()
        {

        }
        public Libro(string titolo, string autore)
        {
            Titolo=titolo;
            Autore=autore;
        }
    }
Record Libro

LA CLASSE StringBuilder

Tale classe che fa parte della BCL (Base Class Library) rappresenta una stringa che però è mutabile a differenza della classe string che è immutabile. Tale classe ci offre un’ampia serie di metodi per modificare la stringa. Il metodo ToString() utilizzato dal compilatore per estrarre informazioni sui record utilizza proprio questa classe. Facciamo ora noi un override del metodo ToString() utilizzando la classe StringBuilder nel record Libro. Qui sotto viene riportato il codice. Utilizzando il comando dotnet run quello che ci ritorna il metodo ToString() è riportato nella seguente schermata.

using System;
using System.Text;

    Libro libro1 = new Libro("L'isola misteriosa","Jules Verne");
    Console.WriteLine(libro1);//LIBRO
    record Libro
    {
        #nullable enable
        public string Titolo {get;init;}
        public string Autore {get;init;}
        
        #nullable disable
        public Libro()
        {

        }
        public Libro(string titolo, string autore)
        {
            Titolo=titolo;
            Autore=autore;
        }
        //OVERRIDE DI ToString()
        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("--RECORD: Libro - ");
            builder.Append(Titolo);
            return builder.ToString();
        }
    }

Il metodo ToString() se applicato ad una istanza della classe StringBuilder restituisce il testo costruito con l’istanza stessa. Adesso riprendiamo il codice del sottotipo del record Libro e nel record LibroDigitale ridefiniamo il metodo ToString(). L’output è mostrato in figura.

using System;
using System.Text;

    Libro libro1 = new Libro("L'isola misteriosa","Jules Verne");
    LibroDigitale libro2 = new LibroDigitale("L'isola misteriosa","Jules Verne", "6 ORE");
    Console.WriteLine(libro1);//LIBRO
    Console.WriteLine(libro2);
    record Libro
    {
        #nullable enable
        public string Titolo {get;init;}
        public string Autore {get;init;}

        #nullable disable
        public Libro()
        {

        }
        public Libro(string titolo, string autore)
        {
            Titolo=titolo;
            Autore=autore;
        }
        //OVERRIDE DI ToString()
        public sealed override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("-- RECORD: Libro - ");
            builder.Append(Titolo);
            return builder.ToString();
        }
    }
    record LibroDigitale:Libro
    {
        #nullable enable
        public string Durata {get;init;}
    
        #nullable disable
        public LibroDigitale()
        {

        }
        public LibroDigitale(string titolo,string autore, string durata)
        {
            Titolo=titolo;
            Autore=autore;
            Durata=durata;
        }
        //OVERRIDE DI ToString
        public override string ToString()
        {
            string s = base.ToString();
            StringBuilder builder = new StringBuilder();
            builder.Append(s);
            builder.Append("-- DURATA: -- ");
            builder.Append(Durata);
            return builder.ToString();
        }
    }
Override ToString() LibroDigitale

LA KEYWORD SEALED

Il C# 10 ci mette a disposizione un metodo per impedire l’override del metodo ToString() nei nostri sotto types record. Inserire la parola chiave sealed nel metodo ridefinito ToString() del record Libro.

using System;
using System.Text;

    Libro libro1 = new Libro("L'isola misteriosa","Jules Verne");
    LibroDigitale libro2 = new LibroDigitale("L'isola misteriosa",Jules Verne", "6 ORE");
    Console.WriteLine(libro1);//LIBRO
    Console.WriteLine(libro2);
    record Libro
    {
        #nullable enable
        public string Titolo {get;init;}
        public string Autore {get;init;}

        #nullable disable
        public Libro()
        {

        }
        public Libro(string titolo, string autore)
        {
            Titolo=titolo;
            Autore=autore;
        }
        //OVERRIDE DI ToString()
        /*--Inserendo la keyword sealed in C# 10 di fatto stiamo
        impedendo l'override del metodo ToString() nei sotto types
        del record Libro cioè non è più possibile fare l'override
        del metodo ToString() nel tipo derivato LibroDigitale.*/
        public sealed override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append("-- RECORD: Libro - ");
            builder.Append(Titolo);
            return builder.ToString();
        }
    }
    record LibroDigitale:Libro
    {
        #nullable enable
        public string Durata {get;init;}
    
        #nullable disable
        public LibroDigitale()
        {

        }
        public LibroDigitale(string titolo,string autore, string durata)
        {
            Titolo=titolo;
            Autore=autore;
            Durata=durata;
        }
        //OVERRIDE DI ToString
        public override string ToString()
        {
            string s = base.ToString();
            StringBuilder builder = new StringBuilder();
            builder.Append(s);
            builder.Append(" -- DURATA: -- ");
            builder.Append(Durata);
            return builder.ToString();
        }
    }

LE RAW STRINGS

Le Raw String Literals (letterali di stringa grezzi) in C# rappresentano una nuova funzionalità introdotta per semplificare la scrittura di stringhe complesse, particolarmente utile quando si lavora con stringhe che contengono caratteri speciali, molteplici linee, o quando si desidera evitare la necessità di escape (caratteri di fuga).

Cos’è una Raw String Literal?

Una Raw String Literal è una stringa che può essere scritta esattamente come appare, senza bisogno di usare caratteri di escape per simboli come le virgolette, le barre inverse (\), o per le nuove linee. Questo rende le raw string literals particolarmente utili per gestire stringhe multi-linea, codice JSON, XML, query SQL, o altre stringhe con formattazione complessa.

Sintassi delle Raw String Literals

La sintassi per le Raw String Literals prevede l’uso di tripli apici doppi (“””) per delimitare la stringa. Ecco un esempio di base:

string rawString = “””
Questo è un esempio di una raw string.
Non c’è bisogno di usare caratteri di escape.
Anche le virgolette doppie ” non devono essere escape.
Puoi scrivere su più linee senza problemi.
“””;

In questo esempio, la stringa rawString contiene esattamente ciò che viene scritto tra i tripli apici doppi, comprese le nuove linee e le virgolette.

Vantaggi delle Raw String Literals

1. Nessun Escaping Necessario: Puoi includere caratteri speciali, come virgolette o barre inverse, direttamente nella stringa senza bisogno di eseguire escaping.

2. Stringhe Multi-linea: Puoi scrivere stringhe che si estendono su più righe, mantenendo il formato originale senza interruzioni o aggiunta di caratteri speciali.

3. Maggiore Leggibilità: Le stringhe che contengono formattazione complessa, come codice o markup, risultano più leggibili e più facili da mantenere.

4. Supporto per Spaziature e Indentazioni: La sintassi delle raw string literals permette di mantenere l’indentazione delle stringhe come desiderato, facilitando la scrittura di codice leggibile.

Esempi di Uso

Esempio 1: Codice JSON

string json = “””
{
“nome”: “Mario”,
“età”: 30,
“indirizzo”: {
      “via”: “Via Roma”,
      “città”: “Roma”
      }
}
“””;

In questo esempio, un codice JSON è inserito direttamente nella stringa senza bisogno di caratteri di fuga o complicazioni.

Esempio 2: Query SQL Multi-linea

string query = “””
SELECT *
FROM utenti
WHERE età > 25
ORDER BY nome ASC;
“””;

Qui, una query SQL viene scritta in più righe mantenendo una chiara e leggibile struttura.

Considerazioni

• Quando si utilizzano le raw string literals, è possibile includere ulteriori apici doppi (ad esempio “”””) all’inizio e alla fine della stringa per evitare conflitti se la stringa contiene tripli apici doppi.

• Le raw string literals sono particolarmente utili in scenari dove è necessario lavorare con stringhe che includono codice, dati strutturati, o testo su più righe, riducendo la necessità di manipolazione e semplificando il mantenimento del codice.

Conclusione

Le Raw String Literals in C# offrono un modo potente e flessibile per gestire stringhe complesse, migliorando significativamente la leggibilità e la manutenibilità del codice. Questa funzionalità rappresenta un importante miglioramento nella gestione delle stringhe, particolarmente utile per sviluppatori che lavorano con formattazioni complesse o stringhe multi-linea.

AUTO-DEFAULT STRUCT

In C# 12 è stata introdotta la funzionalità chiamata Auto-Default Structs, che semplifica ulteriormente l’utilizzo delle strutture (structs) nel linguaggio. Questa funzionalità consente di inizializzare automaticamente le strutture con valori predefiniti senza la necessità di dichiarare esplicitamente un costruttore di default.

Cos’è una Auto-Default Struct?

Una Auto-Default Struct è una struttura che, quando viene istanziata, viene automaticamente inizializzata con i valori di default per tutti i suoi campi, anche se non è stato definito un costruttore di default. Prima di questa funzionalità, le strutture venivano sempre inizializzate con valori predefiniti, ma per ottenere un comportamento personalizzato, era necessario definire esplicitamente un costruttore.

Esempio di Auto-Default Struct

Immaginiamo di avere una struttura Punto che rappresenta un punto 2D:

public struct Punto
{
     public int X;
     public int Y;
}

Con le Auto-Default Structs, è possibile istanziare Punto senza bisogno di un costruttore, e X e Y saranno automaticamente inizializzati ai loro valori predefiniti (che per i tipi numerici è 0):

Punto p = new Punto(); // X = 0, Y = 0

Anche se non si specifica new Punto(), ma si utilizza semplicemente Punto p;, le variabili X e Y vengono automaticamente inizializzate ai loro valori predefiniti.

Vantaggi delle Auto-Default Structs

1. Riduzione del Codice Boilerplate: Non è necessario scrivere esplicitamente un costruttore di default o inizializzare manualmente i campi della struttura.

2. Semplicità: Rende l’uso delle strutture più semplice e immediato, riducendo la possibilità di errori dovuti alla non inizializzazione dei campi.

3. Consistenza: Garantisce che le strutture siano sempre inizializzate in modo prevedibile, con valori di default definiti per i tipi primitivi.

Considerazioni

• Le Auto-Default Structs mantengono il comportamento di default delle strutture in C#, il che significa che non c’è un cambiamento nel modo in cui le strutture vengono trattate a livello di valori predefiniti.

Se una struttura ha campi che devono essere inizializzati con valori diversi dai valori di default, è ancora necessario fornire un costruttore personalizzato.

Esempio di Confronto

Prima di C# 12, per ottenere un’inizializzazione personalizzata, si doveva fare qualcosa di simile:

public struct Punto
{
   public int X;
   public int Y;

   public Punto(int x, int y)
   {
     X = x;
     Y = y;
   }
}

Con Auto-Default Structs, puoi semplicemente dichiarare la struttura senza preoccuparti del costruttore di default per ottenere una corretta inizializzazione:

Punto p = new Punto(); // X = 0, Y = 0

Conclusione

Le Auto-Default Structs in C# 12 migliorano ulteriormente l’usabilità delle strutture, eliminando la necessità di definire costruttori di default e assicurando che tutte le strutture siano inizializzate in modo sicuro e prevedibile. Questa funzionalità riduce il codice boilerplate e rende il linguaggio più snello e intuitivo per gli sviluppatori, migliorando al contempo la leggibilità e la manutenzione del codice.

REQUIRED MEMBERS

La funzionalità dei Required Members in C# 12 è stata introdotta per migliorare la sicurezza del codice e garantire che gli oggetti siano completamente e correttamente inizializzati prima di essere utilizzati. Questa funzionalità consente di dichiarare alcune proprietà o campi di una classe o di una struttura come required, cioè obbligatori, e quindi devono essere necessariamente inizializzati al momento della creazione dell’oggetto.

Cos’è un Required Member?

Un Required Member è una proprietà o un campo che deve essere inizializzato al momento della creazione di un’istanza di una classe o struttura. Se un membro è dichiarato come required e non viene inizializzato nel costruttore o nell’inizializzatore dell’oggetto, il compilatore genererà un errore, garantendo così che il membro venga sempre impostato.

Sintassi dei Required Members

Per dichiarare un membro come required, si utilizza la parola chiave required prima della dichiarazione del membro. Ecco un esempio:

public class Persona
{
    public required string Nome { get; set; }
    public required int Età { get; set; }
    public string? Indirizzo { get; set; }
}

In questo esempio, Nome ed Età sono dichiarati come required, il che significa che devono essere inizializzati al momento della creazione di un’istanza della classe Persona. Indirizzo, invece, è opzionale.

Esempio di Utilizzo

var persona = new Persona
{
      Nome = “Mario“,
      Età = 30
};

Questo codice è corretto perché entrambi i membri required (Nome e Età) sono stati inizializzati.

Vantaggi dei Required Members

1. Inizializzazione Completa: Garantisce che tutte le proprietà essenziali di un oggetto siano inizializzate correttamente, prevenendo errori di runtime dovuti a valori nulli o non inizializzati.

2. Sicurezza del Codice: Riduce la possibilità di errori e bug, obbligando l’inizializzazione completa degli oggetti.

3. Leggibilità: Rende esplicito quali proprietà sono essenziali per l’oggetto, migliorando la comprensione del codice.

Considerazioni

•I Required Members devono essere inizializzati direttamente nel costruttore, nell’inizializzatore dell’oggetto o attraverso un costruttore specifico.

•Questa funzionalità è particolarmente utile nelle situazioni in cui ci si aspetta che determinati membri siano sempre presenti e correttamente impostati per il corretto funzionamento dell’oggetto.

Esempio con Costruttore

È possibile inizializzare i membri required anche tramite un costruttore:

public class Persona
{
        public required string Nome { get; set; }
        public required int Età { get; set; }
        public string? Indirizzo { get; set; }

        public Persona(string nome, int età)
        {
                Nome = nome;
                Età = età;
        }
}

var persona = new Persona(“Mario“, 30);

In questo esempio, il costruttore Persona inizializza i membri required, soddisfacendo i requisiti imposti dal compilatore.

Conclusione

La funzionalità dei Required Members in C# 12 rappresenta un importante passo avanti nella sicurezza e robustezza del codice, garantendo che tutti i membri critici di un oggetto siano sempre inizializzati. Questo rende il codice più sicuro, leggibile e facile da mantenere, prevenendo errori comuni legati all’inizializzazione incompleta degli oggetti.

COSTRUTTORI PRIMARI IN C# 12

I costruttori primari in C# 12 rappresentano una nuova funzionalità introdotta per semplificare la creazione di classi e strutture, rendendo il codice più conciso e facile da leggere. I costruttori primari permettono di definire i parametri del costruttore direttamente nella dichiarazione della classe o della struttura, che possono poi essere utilizzati per inizializzare campi o proprietà.

Persona p = new Persona("Mario", eta: 40);
Console.WriteLine(p);


public class Persona(string nome, int eta)
{
    public string Nome { get; } = nome;
    public int Età { get; } = eta;

    // Metodi o proprietà aggiuntivi possono essere aggiunti qui
    public override string ToString()
    {
        return Nome + " " + Età;
    }
}

In questo esempio, Persona ha un costruttore primario che prende due parametri: nome e età. Questi parametri vengono poi utilizzati per inizializzare le proprietà Nome e Età.

Esempio 2: Costruttore Primario in una Struttura

public struct Punto(int x, int y)
{
    public int X { get; } = x;
    public int Y { get; } = y;

    // Metodi o proprietà aggiuntivi possono essere aggiunti qui
}

Esempio 3: Costruttore Primario con Inizializzazione Aggiuntiva

public class Impiegato(string nome, int età)
{
     public string Nome { get; } = nome;
     public int Età { get; } = età;
     public decimal Stipendio { get; }

     public Impiegato(string nome, int età, decimal stipendio) : this(nome, età)
     {
         Stipendio = stipendio;
     }
}

In questo esempio, Impiegato ha un costruttore primario che inizializza Nome e Età, e un costruttore aggiuntivo che imposta la proprietà Stipendio.

Vantaggi dei Costruttori Primari

1. Concisione: Riduce il codice boilerplate combinando le dichiarazioni del costruttore e delle proprietà.

2. Leggibilità: Rende il codice più facile da leggere e comprendere.

3. Immutabilità: Incoraggia l’uso di proprietà readonly, che possono aiutare a rendere gli oggetti immutabili.

Limitazioni e Considerazioni

•I costruttori primari sono principalmente zucchero sintattico e non introducono nuove funzionalità oltre a quelle già ottenibili con i costruttori tradizionali.

•Potrebbero non essere adatti a tutti gli scenari, specialmente dove è richiesta una logica di inizializzazione più complessa.

Conclusione

I costruttori primari in C# 12 sono un’aggiunta potente al linguaggio, rendendo più facile creare e inizializzare oggetti con meno codice. Migliorano la leggibilità e la manutenibilità, allineandosi con le tendenze moderne di C# verso una sintassi più concisa ed espressiva.

DEFAULT LAMBDA PARAMETER

In C# 12, è stata introdotta la possibilità di specificare parametri di default nelle espressioni lambda. Questa funzionalità permette di assegnare un valore predefinito ai parametri di una lambda, rendendo il codice più flessibile e conciso.

Cos’è un Parametro di Default in una Lambda?

Un parametro di default in una lambda è un valore che viene automaticamente utilizzato se non viene fornito un valore esplicito quando la lambda viene invocata. Questo concetto è simile a quello dei parametri di default nei metodi, ma applicato alle espressioni lambda.

Esempio di Lambda con Parametro di Default

Func<int, int, int> somma = (int a, int b = 10) => a + b;

int risultato1 = somma(5); // Utilizza il valore di default per ‘b’, risultato = 15
int risultato2 = somma(5, 20); // Utilizza il valore esplicito fornito per ‘b’, risultato = 25

In questo esempio:

• La lambda somma ha due parametri: a e b.

•  Il parametro b ha un valore predefinito di 10.

•Quando somma viene invocata con un solo argomento (5), b assume automaticamente il valore di 10.

• Quando somma viene invocata con due argomenti (5 e 20), b assume il valore 20.

Vantaggi dell’uso dei Parametri di Default nelle Lambda

1. Flessibilità: Permette di semplificare l’utilizzo delle lambda quando non è necessario specificare sempre tutti i parametri.

2. Concisione: Riduce il codice ripetitivo e migliora la leggibilità, evitando di dover definire più overload o lambda separate per gestire casi diversi.

3. Manutenibilità: Consente di gestire scenari comuni con meno codice, rendendo più facile apportare modifiche in futuro.

Considerazioni

• Questa funzionalità è particolarmente utile quando si lavora con lambda che hanno parametri opzionali e si vuole evitare la creazione di versioni multiple della stessa lambda.

• È importante ricordare che, come per i parametri di default nei metodi, i parametri con valori predefiniti devono essere posizionati alla fine della lista dei parametri.

Conclusione

I parametri di default nelle lambda in C# 12 rappresentano un’aggiunta che aumenta la flessibilità del linguaggio, rendendo più semplice e chiaro l’uso delle espressioni lambda. Questo permette agli sviluppatori di scrivere codice più conciso e gestire scenari comuni con meno complessità.

COLLECTION EXPRESSION

Le Collection Expressions in C# 12 rappresentano una nuova funzionalità che semplifica la creazione e l’inizializzazione di collezioni (come liste, array, set, ecc.) in modo più conciso e leggibile. Questa funzionalità consente di esprimere la creazione di una collezione utilizzando una sintassi simile a quella delle espressioni lambda o delle object initializers, riducendo la necessità di boilerplate code.

Cos’è una Collection Expression?

Una Collection Expression è un modo compatto e diretto per creare e inizializzare una collezione in C#. Utilizza una sintassi speciale che permette di specificare gli elementi della collezione in modo molto simile a quello utilizzato per definire array o oggetti anonimi.

Esempio di Collection Expression

var numeri = [1, 2, 3, 4, 5];

In questo esempio, numeri è una lista di interi creata utilizzando una Collection Expression. La sintassi [] è stata introdotta per permettere di specificare direttamente gli elementi della collezione all’interno delle parentesi quadre.

Utilizzo Avanzato

Le Collection Expressions possono essere utilizzate anche con altri tipi di collezioni, come ad esempio i dizionari o gli insiemi (set).

Esempio con un Dizionario

var dizionario = [“chiave1” => “valore1“, “chiave2” => “valore2“];

In questo esempio, dizionario è un dizionario creato e inizializzato utilizzando una Collection Expression. La sintassi => viene utilizzata per mappare chiavi a valori.

Esempio con una List e Operazioni Complesse

var lista = [1, 2, 3, 4, 5].Where(x => x % 2 == 0).ToList();

Qui, una lista di numeri viene inizializzata e successivamente filtrata usando LINQ per ottenere solo i numeri pari, il tutto in un’unica espressione.

Vantaggi delle Collection Expressions

1. Concisione: Permette di inizializzare collezioni con una sintassi molto più breve rispetto ai metodi tradizionali.

2. Leggibilità: Il codice risulta più leggibile e immediato, riducendo il rumore visivo dovuto a chiamate esplicite ai costruttori delle collezioni.

3. Flessibilità: Può essere utilizzata in combinazione con altre funzionalità di C# come LINQ, rendendo le operazioni sulle collezioni più fluide.

Considerazioni

• Le Collection Expressions sono progettate per migliorare la sintassi, ma è importante utilizzarle in modo appropriato per mantenere la leggibilità del codice.

• Questa funzionalità si integra bene con le altre caratteristiche del linguaggio, come i target-typed expressions, dove il tipo di collezione viene dedotto dal contesto.

Conclusione

Le Collection Expressions in C# 12 offrono un modo più elegante e conciso di lavorare con le collezioni, riducendo la verbosità e aumentando la leggibilità del codice. Questa funzionalità rappresenta un ulteriore passo avanti nella modernizzazione della sintassi di C#, allineandola con le esigenze degli sviluppatori di scrivere codice pulito ed efficiente.

LINK AL CODICE C# USATO NEL POST

GITHUB