Condividi tramite


Struct C#

Suggerimento

Novità dello sviluppo di software? Iniziare prima con le esercitazioni introduttive . Quando avrai bisogno di tipi di valore leggeri nel tuo codice, incontrerai le struct.

Esperienza in un'altra lingua? Gli struct C# sono tipi valore simili agli struct in C++ o Swift, ma si trovano nell'heap gestito quando confinati e supportano interfacce, costruttori e metodi. Osservare la sezione readonly struct per i modelli specifici di C#. Per le strutture di record, vedere Record.

Uno struct è un tipo di valore che contiene i dati direttamente nell'istanza, anziché tramite un riferimento a un oggetto nell'heap. Quando si assegna uno struct a una nuova variabile, il runtime copia l'intera istanza. Le modifiche apportate a una variabile non influiscono sull'altra perché ogni variabile rappresenta un'istanza diversa. Usare gli struct per i tipi piccoli e leggeri il cui ruolo primario è archiviare dati piuttosto che quello di modellare il comportamento. Gli esempi includono coordinate, colori, misurazioni o impostazioni di configurazione.

Quando usare gli struct

Usare una struct quando il tuo tipo:

  • Rappresenta un singolo valore o un piccolo gruppo di valori correlati (approssimativamente 16 byte o minore).
  • Ha una semantica di valori: due istanze con gli stessi dati devono essere uguali.
  • È principalmente un contenitore di dati anziché un modello di comportamento.
  • Non è necessaria l'ereditarietà da un tipo di base (gli struct non possono ereditare da altri struct o classi, ma possono implementare interfacce).

Per un confronto più ampio che include classi, record, tuple e interfacce, vedere Scegliere il tipo di tipo.

Dichiarare una struct

Definire uno struct con la parola chiave struct. Uno struct può contenere campi, proprietà, metodi e costruttori, proprio come una classe:

struct Point
{
    public double X { get; set; }
    public double Y { get; set; }

    public readonly double DistanceTo(Point other)
    {
        var dx = X - other.X;
        var dy = Y - other.Y;
        return Math.Sqrt(dx * dx + dy * dy);
    }

    public override string ToString() => $"({X}, {Y})";
}

Lo Point struct archivia due double valori e fornisce un metodo per calcolare la distanza tra due punti. Il DistanceTo metodo è contrassegnato readonly perché non modifica lo stato dello struct. Questo schema è coperto da membri di sola lettura.

Semantica dei valori

Gli struct sono tipi valore. L'assegnazione copia i dati, quindi ogni variabile contiene la propria copia indipendente:

var p1 = new Point { X = 3, Y = 4 };
var p2 = p1; // copies the data
p2.X = 10;

Console.WriteLine(p1); // (3, 4)  — p1 is unchanged
Console.WriteLine(p2); // (10, 4) — only p2 was modified

Poiché i struct sono contenitori di dati, l'assegnazione copia ogni membro di dati in una nuova istanza indipendente. Ogni copia è distinta. La modifica di una non influisce sull'altra. Questo comportamento è diverso dalle classi, in cui l'assegnazione copia solo il riferimento e entrambe le variabili condividono lo stesso oggetto. Per altre informazioni sulla distinzione, vedere Tipi di valore e tipi di riferimento.

Costruttori di struct

È possibile definire costruttori negli struct nello stesso modo in cui si esegue nelle classi. Gli struct possono avere costruttori senza parametri che impostano valori predefiniti personalizzati. Il termine "costruttore senza parametri" distingue un'istanza creata con new (che esegue la logica del costruttore) da un'istanza predefinita creata con l'espressione default (che inizializza zero-tutti i campi):

struct ConnectionSettings
{
    public string Host { get; set; }
    public int Port { get; set; }
    public int MaxRetries { get; set; }

    public ConnectionSettings()
    {
        Host = "localhost";
        Port = 8080;
        MaxRetries = 3;
    }
}

Un costruttore senza parametri viene eseguito quando si usa new senza argomenti. L'espressione default ignora il costruttore e imposta tutti i campi sui relativi valori predefiniti (0, null, false). Tenere presente la differenza:

var custom = new ConnectionSettings();
Console.WriteLine($"{custom.Host}:{custom.Port} (retries: {custom.MaxRetries})");
// localhost:8080 (retries: 3)

var defaults = default(ConnectionSettings);
Console.WriteLine($"{defaults.Host ?? "(null)"}:{defaults.Port} (retries: {defaults.MaxRetries})");
// (null):0 (retries: 0)

Il compilatore inizializza automaticamente tutti i campi non impostati in modo esplicito in un costruttore. È possibile inizializzare solo i campi che richiedono valori non predefiniti:

struct GameTile
{
    public int Row { get; set; }
    public int Column { get; set; }
    public bool IsBlocked { get; set; }

    public GameTile(int row, int column)
    {
        Row = row;
        Column = column;
        // IsBlocked is automatically initialized to false
    }
}

Nell'esempio seguente viene visualizzato il valore predefinito per IsBlocked:

var tile = new GameTile(2, 5);
Console.WriteLine($"Tile ({tile.Row}, {tile.Column}), blocked: {tile.IsBlocked}");
// Tile (2, 5), blocked: False

La IsBlocked proprietà non viene assegnata nel costruttore, quindi il compilatore lo imposta su false (impostazione predefinita per bool). Questa funzionalità riduce il boilerplate nei costruttori che devono solo impostare alcuni campi.

Struct di sola lettura e membri di sola lettura

Un readonly struct garantisce che nessun membro di istanza modifichi lo stato della struttura. Il compilatore applica questa garanzia richiedendo che tutti i campi e le proprietà implementate automaticamente siano di sola lettura:

readonly struct Temperature
{
    public double Celsius { get; }

    public Temperature(double celsius) => Celsius = celsius;

    public double Fahrenheit => Celsius * 9.0 / 5.0 + 32.0;

    public override string ToString() => $"{Celsius:F1}°C ({Fahrenheit:F1}°F)";
}

Nell'esempio seguente viene creata un'istanza Temperature di e vengono lette le relative proprietà:

var temp = new Temperature(100);
Console.WriteLine(temp); // 100.0°C (212.0°F)
// temp.Celsius = 50; // Error: property is read-only

Quando non è necessario che l'intero struct sia non modificabile, contrassegnare invece i singoli membri come readonly . Un readonly membro non può modificare lo stato dello struct e il compilatore verifica tale garanzia:

struct Velocity
{
    public double X
    {
        readonly get;
        set;
    }

    public double Y
    {
        readonly get;
        set;
    }

    public readonly double Speed => Math.Sqrt(X * X + Y * Y);

    public readonly override string ToString() => $"({X}, {Y}) speed={Speed:F2}";
}

L'esempio seguente mostra che readonly i membri restituiscono valori aggiornati quando cambiano le proprietà modificabili:

var v = new Velocity { X = 3, Y = 4 };
Console.WriteLine(v.Speed); // 5
Console.WriteLine(v);       // (3, 4) speed=5.00
v.X = 6;
Console.WriteLine(v.Speed); // 7.211...

Contrassegnare i membri readonly consente al compilatore di ottimizzare le copie difensive. Quando si passa un readonly struct a un metodo che accetta un in parametro, il compilatore sa che non è necessaria alcuna copia.

Vedere anche