Tipi strutturati

 

Dati

 

Le classi offrono svariati modi per incapsulare gli oggetti nei propri programmi. Sono memorizzate nello heap in modo da aver più flessibilità nel ciclo di vita dei dati, ma con un costo minore in termini di prestazioni. Questo costo è piccolo grazie alle ottimizzazioni degli heap gestiti. In alcune situazioni, però, l' unica cosa di cui si ha veramente bisogno sono delle strutture dati piccole. In questo caso una classe da molte più funzionalità di quante ne servono e per motivi di prestazioni si preferirebbe usare un tipo strutturato.

Si guardi questo esempio:

 

 

class Dimensioni
{
    public double lunghezza;
    public double larghezza;
}

 

 

 

Questo codice definisce una classe denominata Dimensioni, che memorizza semplicemente la lunghezza e larghezza di qualcosa magari si sta scrivendo un programma di gestione dell'arredamento, che consenta alle persone di sperimentare diverse disposizioni dei propri mobili sul computer e si vuole memorizzare le dimensioni di ogni pezzo dell' arredamento. Sembra quasi che stiamo infrangendo le regole della buona programmazione rendendo i campi pubblici, ma il concetto è che non si ha davvero bisogno di tutte le agevolazioni di una classe. Tutto quello che si ha sono due numeri, che si trova conveniente trattare come coppia piuttosto che individualmente. Non c' è bisogno di metodi o di ereditare altre classi e sicuramente non si vuole che il runtime .NET si occupi di portare tutto nello heap con tutte le implicazioni in termini di prestazioni, solo per numeri a doppia precisione.

Come già detto all' inizio, l' unica cosa che bisogna modificare nel codice per passare da un tipo definito come classe ad un tipo strutturato è di sostituire la parola chiave class con struct.

 

struct Dimensioni
{
    public double lunghezza;
    public double larghezza;

    Dimensioni(double Lunghezza, double Larghezza)
    {
        lunghezza=Lunghezza;
        larghezza=Larghezza;
    }

    public double Diagonale
    {
         get
         {
              return Math.Sqrt(lunghezza*lunghezza+larghezza*larghezza);
         }
    }

}

 

 

 

E' possibile pensare ai tipi strutturati di C# come a delle classi in piccolo. Sono sostanzialmente la stessa cosa, ma pensati nei casi in cui si vuole semplicemente tenere insieme i dati. Differiscono dalle classi per i seguenti motivi:

§ I tipi strutturati sono tipi di dato valore, non tipi riferimento, questo significa che sono memorizzati nello stack o sostituti ( se sono    parte di un altro oggetto memorizzato nello heap) e hanno lo stesso ciclo di vita e le restrizioni dei tipi dato semplici.

 

§ I tipi strutturati non supportano l' ereditarietà

 

§ Ci sono differenze nel modo in cui i costruttori lavorano con i tipi strutturati. In particolare, il compilatore fornisce sempre un          

costruttore  di default senza parametri, che non è possibile rimpiazzare.

 

§ Con un tipo strutturato, è possibile specificare come i campi vengono disposti in memoria.

 

Poiché i tipi strutturati sono pensati per raggruppare i dati, si noterà che molti o tutti i loro campi sono dichiarati come pubblici. Questo, in poche parole, è contrario a scrivere codice il codice in .NET ( secondo Microsoft i campi che sono const, dovrebbero essere sempre privati incapsulati in proprietà pubbliche). Comunque, per i tipi di dato semplici molti sviluppatori, non riterrebbero mai i campi pubblici una buona pratica di programmazione.

 

I tipi strutturati sono tipi di valore

 

Sebbene i tipi strutturati siano tipi di valore, è possibile trattarli sintatticamente allo stesso modo delle classi. Ad esempio , con la definizione della classe Dimensioni, in precedenza, si potrebbe scrivere:

 

Dimensioni punto= new Dimensioni();

punto.lunghezza=3;

punto.larghezza=6;

 

Si noti che poiché i tipi strutturati sono tipi di valore, l' operatore new non funziona allo stesso modo delle classi e altri tipi referenziati. Al posto di allocare nello heap, l' operatore new chiama semplicemente il costruttore appropriato in base ai parametri passati, inizializzando tutti i campi. In verità per i tipi strutturati è del tutto accettabile scrivere:

 

Dimensioni punto;

punto.lunghezza=3;

punto.larghezza=6;

 

Se Dimensioni fosse una classe, questo produrrebbe un errore di compilazione poiché punto conterrebbe un riferimento non inizializzato (un indirizzo che non punta da nessuna parte, cosicché non sarebbe possibile impostare valori per il campo). Per un tipo strutturato, invece, la dichiarazione della variabile alloca spazio nello stack per tutta la struttura, cosicché sia pronta ad assegnare i valori. Si noti, comunque, che il seguente codice porterebbe ad un errore di compilazione, con il compilatore che si lamenterebbe dell' aver utilizzato una variabile non inizializzata.

 

Dimensioni punto;

punto.lunghezza=3;

 

I tipi strutturati seguono le stesse regole di ogni altro tipo di dato: ogni cosa deve essere inizializzata prima di essere utilizzata. Un tipo di dato strutturato, si considera completamente inizializzato quando viene chiamato il relativo operatore new o quando ai suoi campi sono stati assegnati individualmente tutti i valori. E, ovviamente, un tipo strutturato definito come campo membro di una classe è inizializzato attraverso l'azzeramento automatico quando l' oggetto contenitore viene inizializzato.

Il fatto che i tipi strutturati siano tipi di valore influenzerà le prestazioni, e in base a come vengono utilizzate, questo può avvenire in senso positivo o negativo. Positivo, perché l' allocazione della memoria per i tipi strutturati avviene molto velocemente poiché ha luogo inline (durante la compilazione) o nello stack. Lo stesso si può dire della rimozione dei tipi strutturati quando non ha più utilizzi.

D' altro canto, però, ogni volta che si passa un tipo strutturato come parametro, o si assegna un tipo strutturato ad un altro strutturato ( come A=B dove A e B sono tipi strutturati), l' intero contenuto del tipo strutturato viene copiato, mentre per una classe viene copiato solo il riferimento. Questo risulta in un calo di prestazioni che dipende dalla dimensione del tipo strutturato (questo dovrebbe evidenziare il fatto che i tipi strutturati sono pensati come strutture di dati molto piccole). Si noti, comunque, che quando si passa un tipo strutturato come parametro ad un metodo, si può evitare il calo di prestazioni passando come parametro ref, in questo caso viene passato solo l' indirizzo di memoria del tipo strutturato, ed il passaggio avviene alla stessa velocità con cui si passa ad una classe. Facendo questo, però bisogna stare attenti perché significa che il metodo chiamato può in linea di principio, cambiare il valore del tipo strutturato.

 

Tipi strutturati ed ereditarietà

 

I tipi strutturati non sono pensati per l' ereditarietà. Questo significa che non è possibile ereditare da un tipo strutturato. L' unica eccezione è che un tipo strutturato, allo stesso modo di ogni altro tipo C#, deriva in ultima analisi dalla classe System.Object. Quindi, i tipi strutturati hanno accesso ai metodi di System.Object ed anche possibile sovrascriverli (un esempio ovvio potrebbe essere la sovrascrittura del metodo ToString()). La reale catena di ereditarietà per i tipi strutturati è che ogni tipo strutturato deriva da una classe , System.ValueType, che a sua volta deriva da System.Object. ValueType non aggiunge nessun altro membro a Object, ma fornisce l' implementazione di alcuni di questi che sono più adatti a tipi strutturati. Si noti che non è possibile una classe base derivata per un tipo strutturato. Ogni tipo strutturato è derivato da ValueType.

 

Costruttori per tipi strutturati

 

E' possibile definire costruttori per i tipi strutturati allo stesso modo delle classi, eccetto che non è possibile definire un costruttore senza parametri. Questo sembra insensato, e la ragione è nell' implementazione del runtime .NET. In alcune rare circostanze succede che il runtime .NET non è in grado di chiamare un costruttore senza parametri che viene fornito. Microsoft ha preso la via più facile, abolendo i costruttori senza parametri per i tipi strutturati.

Detto questo, il costruttore di default, che inizializza tutti i campi azzerandoli è sempre presente implicitamente, anche se si fornisce un altro costruttore che prende parametri. È impossibile aggirare il costruttore di default passando valori iniziali per i campi. Il codice seguente risulterà in un errore di compilazione:

 

struct Dimensione

{

  public double lunghezza=1; // error, invalid value not allowed

  public double larghezza=1; // error, invalid value not allowed

}

Ovviamente, se Dimensione fosse stata una classe, questo codice sarebbe stato compilato senza problemi.

 

È anche possibile, fornire i metodi close() o Dispose() per un tipo strutturato allo stesso modo di una classe.