C#

STRUCT

Un primo passo vero le classi è costituito dalle struct. Si tratta di un tipo valore che viene usato per raggruppare piccoli insiemi di variabili logicamente correlate così da definire una entità logica. La differenza principale rispetto ad una classe è proprio il fatto che si tratta di un tipo valore, quindi vive nello stack. Inoltre una struct non supporta l'ereditarietà, discende da System.ValueType (che, a sua volta, come già detto, discente da System.Object) ma a sua volta non ha successori, vedremo parlando delle classi cosa significa questo concetto. Possono derivare da interfacce ma non da altre struct e anhche questa facoltà sarà illustarta insieme alle classi. Esistono altre differenze, diciamo minori, ma altrettanto importanti: non possono avere costruttori privi di parametri, virtualizzatori e membri virtuali. (NB: In C#6 si era pensato di introdurre la possibilità di avere costruttori privi di parametri per le struct; problemi di stabilità hanno suggerito di tralasciare, almeno per ora, questa feature). Di tutte queste cose riparleremo, per ora prendetele così come sono. Le struct possono peraltro contenere, oltre alle variabili (campi) anche costruttori, metodi, indicizzatori, operatori, eventi e tipi nidificati. Le struct, come abbiamo visto, sono pesantemente utilizzate all'interno del framework stesso. Sull'uso di una classe rispetto ad una struttura ci sono parecchie spiegazioni molto approfondite sul web, non starò ad aggiungere troppe parole in merito. In generale tipi custom "piccoli" che non necessitano di grosse interazioni successive alla loro definizioni possono essere spesso favorevolmente espressi con delle struct. E qui mi fermo, rimandandovi a quanto disponibile in rete.
Formalmente abbiamo:

modificatore struct nomestruct
{
membri interni
}

vediamo un primo esempio:

  Esempio 17.1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;

struct Punto
{
  public int xcoor;
  public int ycoor;
  public int zcoor;
}

class program
{
  public static void Main()
  {
    Punto p1;
    p1.xcoor = 1;
    p1.ycoor = 2;
    p1.zcoor = 3;
  }
}

La nostra prima struct viene definita alla riga 3. Essa viene richiamata alla 14 e le sue proprietà sono inizializzate dalle righe dalla 15 alla 17. Una prima regola da ricordare è che non possono essere eseguite inizializzazioni all'interno della struct stessa. Se ci provate ne avrete un errore in fase di compilazione.
Le struct possono essere caratterizzate da un costruttore, ovvero un meccanismo, che rivedremo pari pari nelle classi, che è utile per inizializzare tutti o una parte di valori interni alla struct. Come detto poco sopra, non possono esservi costruttori privi di parametri.

  Esempio 17.2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

struct Punto
{
  public int xcoor;
  public int ycoor;
  public int zcoor;
  public Punto(int xcoor, int ycoor, int zcoor)
  {
    this.xcoor = xcoor;
    this.ycoor = ycoor;
    this.zcoor = zcoor;
  }
}

class program
{
  public static void Main()
  {
    Punto p1 = new Punto(1,2,3);
    Console.WriteLine(p1.ycoor);
  }
}

Il costruttore viene definito alla riga 8 e la sua presenza facilità l'iniziazione delle istanze della struct. In questo caso l'inizializzazione è sicuramente più rapida e leggibile, nulla osta che possiate modificare il valore di ogni singolo elemento. Alla riba 20 noterete la keyword new, usata in questo caso per generare una nuova istanza della struct. E un secondo modo per creare una variabile, il pri mo lo abbiamo incontrato alla riga 14 dell'esempio 17.1. L'uso dei costruttori è possibile anche in forma semplificata rispetto a quella "classica" dell'esempio precedente:

  Esempio 17.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using System;

struct Saluto
{
  public string s1;
  public string s2;
  public Saluto(string ss1, string ss2)
  {
    s1 = ss1;
    s2 = ss2;
  }
}

class program
{
  public static void Main()
  {
    Saluto sal1 = new Saluto("W ", "la Juve");
    Console.WriteLine(sal1.s1 + sal1.s2);
  }
}

All'interno di una struct possiamo anche trovare dei metodi:

  Esempio 17.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System;

struct Saluto
{
  public string s1;
  public string s2;
  public void Hello()
  {
    Console.WriteLine(s1 + s2);
  }
}

class program
{
  public static void Main()
  {
    Saluto sal1;
    sal1.s1 = "Ciao, ";
    sal1.s2 = "Mondo!";
    sal1.Hello();
  }
}

Il metodo definito alla riga 7 viene poi richiamato alla 20, molto semplicemente.
La copia di una struttura in un'altra non crea problemi:

  Esempio 17.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

struct Punto
{
  public int x;
  public int y;
}

class program
{
  public static void Main()
  {
    Punto p1;
    p1.x = 2;
    p1.y = 3;
    Punto p2 = p1;
    Console.WriteLine(p2.x);
    Console.WriteLine(p2.y);
    p1.x = 8;
    Console.WriteLine(p2.x);
    Console.WriteLine(p2.y);
  }
}

La variabile p2 viene creata ad immagine e somiglianza di p1. Tuttavia resta indipendente da questo, come è logico visto che parliamo di value types e questo è dimostrato dal fatto che modificando un valore in p1, vedasi riga 19, non si ha alcuna conseguenza sui valori di p2. D'altronde x e y definiti all'interno di Punto, alle righe 5 e 6, sono a loro volta value types. E se all'interno di una struct ci fosse un reference type, come un array? Questo è diverso:

  Esempio 17.6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System;

struct Vettore
{
  public int[] arr1;
}

class program
{
  public static void Main()
  {
    Vettore v1;
    v1.arr1 = new int[] { 1, 2, 3 };
    Vettore v2 = v1;
    v2.arr1[1] = 9;
    Console.WriteLine(v1.arr1[1]);
  }
}

Eseguendo questo programma noterete subito che la variazione indotta su un elemento dell'array interno a v2 ha effetto anche su quello interno a v1. I tipi reference, anche se all'interno di una struct vengono passati per riferimento con tutto quanto ne consegue e di cui abbiammo ampiamente parlato. La cosa è intuibile ma qui l'abbiamo formalizzata.

Come si comporta il nostro IL in presenza di una struct? Ad esempio, con questo semplice codice:

struct St1
{
  public int x;
}

class program
{
  public static void Main()
  {
    St1 st1;
    st1.x = 5;
  }
}

ecco qua:

.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 10 (0xa)
.maxstack 2
.locals init (valuetype St1 V_0)
IL_0000: nop
IL_0001: ldloca.s V_0
IL_0003: ldc.i4.5
IL_0004: stfld int32 St1::x
IL_0009: ret
} // end of method program::Main

Nella riga evidenziata viene effettuata l'inizializzazione del campo x.

L'argomento relativo alle struct sarà brevemente mripreso quando parleremo delle interfaccie. Per ora dovrebbero essere sufficiente questi cenni per lavorare con questo importante costrutto.