Qual è la differenza tra struct e class in .NET?


Risposte:


1058

In .NET, ci sono due categorie di tipi, tipi di riferimento e tipi di valore .

Le strutture sono tipi di valore e le classi sono tipi di riferimento .

La differenza generale è che un tipo di riferimento vive nell'heap e un tipo di valore vive in linea, ovvero ovunque sia definita la variabile o il campo.

Una variabile che contiene un tipo di valore contiene l'intero valore del tipo di valore. Per una struttura, ciò significa che la variabile contiene l'intera struttura, con tutti i suoi campi.

Una variabile che contiene un tipo di riferimento contiene un puntatore o un riferimento a un'altra parte della memoria in cui risiede il valore effettivo.

Questo ha un vantaggio, per cominciare:

  • i tipi di valore contengono sempre un valore
  • i tipi di riferimento possono contenere riferimenti null , nel senso che al momento non si riferiscono affatto a nulla

Internamente, i tipi di riferimento sono implementati come puntatori e sapendo che, e sapendo come funziona l'assegnazione delle variabili, ci sono altri modelli comportamentali:

  • copiando il contenuto di una variabile del tipo di valore in un'altra variabile, copia l'intero contenuto nella nuova variabile, distinguendo i due. In altre parole, dopo la copia, le modifiche a una non influiranno sull'altra
  • copiando il contenuto di una variabile del tipo di riferimento in un'altra variabile, il riferimento viene copiato, il che significa che ora si hanno due riferimenti alla stessa memoria altrove dei dati effettivi. In altre parole, dopo la copia, la modifica dei dati in un riferimento sembrerà influenzare anche l'altro, ma solo perché stai davvero guardando gli stessi dati in entrambe le posizioni

Quando dichiari variabili o campi, ecco come differiscono i due tipi:

  • variabile: il tipo di valore risiede nello stack, il tipo di riferimento risiede nello stack come puntatore a un punto della memoria dell'heap in cui risiede la memoria effettiva (sebbene si noti la serie di articoli di Eric Lipperts: Lo stack è un dettaglio di implementazione ).
  • class / struct-field: il tipo di valore vive completamente all'interno del tipo, il tipo di riferimento vive all'interno del tipo come puntatore a un punto della memoria dell'heap in cui vive la memoria effettiva.

43
Nell'interesse della piena completezza, dovrei ricordare che Eric Lippert ha affermato che lo stack è un dettaglio di implementazione , ogni volta che menziono lo stack sopra, ho in mente i post di Eric.
Lasse V. Karlsen,

2
È tutto valido anche per C ++?
Koray Tugay,

9
un'altra differenza cruciale è l'utilizzo. Da MSDN: "Le strutture sono in genere utilizzate per incapsulare un piccolo gruppo di variabili correlate, come le coordinate del rettangolo. Le strutture possono anche contenere costruttori, costanti, campi, metodi, proprietà, indicizzatori, operatori, eventi e tipi nidificati, anche se diversi sono richiesti membri, dovresti prendere in considerazione la possibilità di rendere il tuo tipo una classe ".
thewpfguy,

4
@KorayTugay No, non lo è.
Zoom:

9
@KorayTugay in C ++ struct e class sono assolutamente equivalenti ad eccezione dell'unica cosa: restrizione di accesso predefinita (la classe è privata per impostazione predefinita, struct ha pubblico)
berkus

207

Un breve riassunto di ciascuno:

Solo lezioni:

  • Può supportare l'ereditarietà
  • Sono tipi di riferimento (puntatore)
  • Il riferimento può essere nullo
  • Sovraccarico di memoria per nuova istanza

Solo strutture:

  • Impossibile supportare l'ereditarietà
  • Sono tipi di valore
  • Sono passati per valore (come numeri interi)
  • Impossibile avere un riferimento null (a meno che non venga utilizzato Nullable)
  • Non avere un sovraccarico di memoria per nuova istanza, a meno che non sia "inscatolato"

Sia classi che strutture:

  • Sono tipi di dati composti tipicamente utilizzati per contenere alcune variabili che hanno una relazione logica
  • Può contenere metodi ed eventi
  • Può supportare interfacce

16
Ci sono alcune parti di questa risposta che non sono del tutto giuste. Le classi non vanno sempre in pila e le strutture non vanno sempre in pila. Le attuali eccezioni includono campi di struttura su una classe, variabili acquisite in metodi anonimi ed espressioni lambda, blocchi iteratori e valori già inscatolati. Tuttavia, l'allocazione stack vs heap è un dettaglio di implementazione e può essere soggetta a modifiche. Eric lippart ne discute qui . Ho effettuato il downgrade, ma lo rimuoverò felicemente se aggiorni.
Simon P Stevens,

1
struct non supporta l'ereditarietà di altre strutture / classi, ma PUOI implementare un'interfaccia su una struct.
thewpfguy,

2
Potresti voler chiarire cosa intendi quando affermi che le strutture "Non hanno un overhead di memoria per nuova istanza" . La mia prima interpretazione fu che stavi sostenendo - ovviamente assurdamente - che le strutture usano zero memoria. Poi ho pensato che forse stai cercando di dire che una struttura, a differenza di una classe, richiede esattamente la stessa quantità di memoria della somma dei suoi campi membri e non di più. Ma poi ho cercato su Google c# struct memory overheade ho trovato questa risposta di Hans Passant che dice che no, non è nemmeno il caso. Così che cosa vuoi dire?
Mark Amery,

4
@MarkAmery Ho avuto la stessa reazione iniziale del tuo id all'espressione "nessun sovraccarico di memoria", ma penso che l'OP si riferisca al fatto che le istanze di classsono memoria gestita (gestita dal garbage collector), mentre le istanze di structnon lo sono .
Hutch,

1
"Struct sono passati per valore (come numeri interi)" è falso: tutte le variabili vengono passate per valore, anche il tipo di riferimento. Se si desidera passare una variabile per riferimento, è necessario utilizzare la parola chiave "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli

41

In .NET le dichiarazioni di struct e class distinguono tra tipi di riferimento e tipi di valore.

Quando si passa attorno a un tipo di riferimento, ce n'è solo uno effettivamente memorizzato. Tutto il codice che accede all'istanza accede allo stesso.

Quando si passa attorno a un tipo di valore, ognuno è una copia. Tutto il codice funziona su una sua copia.

Questo può essere mostrato con un esempio:

struct MyStruct 
{
    string MyProperty { get; set; }
}

void ChangeMyStruct(MyStruct input) 
{ 
   input.MyProperty = "new value";
}

...

// Create value type
MyStruct testStruct = new MyStruct { MyProperty = "initial value" }; 

ChangeMyStruct(testStruct);

// Value of testStruct.MyProperty is still "initial value"
// - the method changed a new copy of the structure.

Per una classe questo sarebbe diverso

class MyClass 
{
    string MyProperty { get; set; }
}

void ChangeMyClass(MyClass input) 
{ 
   input.MyProperty = "new value";
}

...

// Create reference type
MyClass testClass = new MyClass { MyProperty = "initial value" };

ChangeMyClass(testClass);

// Value of testClass.MyProperty is now "new value" 
// - the method changed the instance passed.

Le classi non possono essere nulla: il riferimento può puntare a un null.

Le strutture sono il valore reale: possono essere vuote ma mai nulle. Per questo motivo le strutture hanno sempre un costruttore predefinito senza parametri - hanno bisogno di un "valore iniziale".


@Todua sì, ci sono risposte migliori sopra, che ho votato e scelto come risposta dopo aver fornito questo - questo è dalla prima beta di SO quando stavamo ancora capendo le regole.
Keith,

1
Non so se mi hai capito correttamente, ho davvero votato / accettato la tua risposta (al contrario delle risposte sopra), perché la tua ha avuto buoni esempi (non solo spiegazione teorica, al contrario della risposta sopra, che aveva solo spiegazioni teoriche senza esempi ).
T.Todua,

24

Differenza tra strutture e classi:

  • Le strutture sono di tipo valore mentre le classi sono di tipo di riferimento .
  • Le strutture sono archiviate nello stack mentre le classi sono archiviate nell'heap .
  • I tipi di valore mantengono il loro valore nella memoria in cui sono dichiarati, ma il tipo di riferimento contiene un riferimento a una memoria oggetto.
  • Tipi di valore distrutti immediatamente dopo la perdita dell'ambito mentre il tipo di riferimento viene distrutto solo dalla variabile dopo la perdita dell'ambito. L'oggetto viene successivamente distrutto dal Garbage Collector.
  • Quando copi una struttura in un'altra struttura, una nuova copia di quella struttura viene modificata e modificata di una struttura non influisce sul valore dell'altra struttura.
  • Quando copi una classe in un'altra classe, copia solo la variabile di riferimento.
  • Entrambe le variabili di riferimento puntano allo stesso oggetto sull'heap. La modifica a una variabile influirà sull'altra variabile di riferimento.
  • Le strutture non possono avere distruttori , ma le classi possono avere distruttori.
  • Le strutture non possono avere espliciti costruttori senza parametri mentre una classe può strutturare non supporta l'ereditarietà, ma le classi lo fanno. Entrambi supportano l'ereditarietà da un'interfaccia.
  • Le strutture sono di tipo sigillato .

21

Dalla scelta di Microsoft tra Class e Struct ...

Come regola generale, la maggior parte dei tipi in un framework dovrebbe essere di classe. Vi sono, tuttavia, alcune situazioni in cui le caratteristiche di un tipo di valore rendono più appropriato l'uso di strutture.

CONSIDERARE una struttura anziché una classe:

  • Se le istanze del tipo sono piccole e comunemente di breve durata o sono comunemente incorporate in altri oggetti.

X EVITARE una struttura a meno che il tipo non abbia tutte le seguenti caratteristiche:

  • Rappresenta logicamente un singolo valore, simile ai tipi primitivi (int, double, ecc.).
  • Ha una dimensione dell'istanza inferiore a 16 byte.
  • È immutabile (non modificabile)
  • Non dovrà essere inscatolato frequentemente.

19

Oltre a tutte le differenze descritte nelle altre risposte:

  1. Le strutture non possono avere un costruttore esplicito senza parametri mentre una classe può
  2. Le strutture non possono avere distruttori , mentre una classe può
  3. Le strutture non possono ereditare da un'altra struttura o classe mentre una classe può ereditare da un'altra classe. (Sia le strutture che le classi possono essere implementate da un'interfaccia.)

Se stai cercando un video che spieghi tutte le differenze, puoi dare un'occhiata alla Parte 29 - Tutorial C # - Differenza tra classi e strutture in C # .


4
Molto più significativo del fatto che i linguaggi .net generalmente non permetteranno a una struttura di definire un costruttore senza parametri (la decisione se consentire o meno che sia fatta dal compilatore del linguaggio) è il fatto che le strutture possono nascere ed essere esposte verso il mondo esterno senza che sia stato eseguito alcun tipo di costruttore (anche quando viene definito un costruttore senza parametri). Il motivo per cui i linguaggi .net generalmente vietano i costruttori senza parametri per le strutture è di evitare la confusione che deriverebbe dal fatto che tali costruttori vengano eseguiti a volte e talvolta no.
supercat,

15

Le istanze delle classi vengono archiviate nell'heap gestito. Tutte le variabili "contenenti" un'istanza sono semplicemente un riferimento all'istanza nell'heap. Il passaggio di un oggetto a un metodo comporta il passaggio di una copia del riferimento, non dell'oggetto stesso.

Le strutture (tecnicamente, i tipi di valore) sono archiviate ovunque vengano utilizzate, proprio come un tipo primitivo. Il contenuto può essere copiato dal runtime in qualsiasi momento e senza invocare un costruttore di copie personalizzato. Il passaggio di un tipo di valore a un metodo comporta la copia dell'intero valore, sempre senza richiamare alcun codice personalizzabile.

La distinzione è resa migliore dai nomi C ++ / CLI: "ref class" è una classe come descritta per prima, "value class" è una classe come descritta per seconda. Le parole chiave "class" e "struct" utilizzate da C # sono semplicemente qualcosa che deve essere appreso.


11
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
|                        |                                                Struct                                                |                                               Class                                               |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| Type                   | Value-type                                                                                           | Reference-type                                                                                    |
| Where                  | On stack / Inline in containing type                                                                 | On Heap                                                                                           |
| Deallocation           | Stack unwinds / containing type gets deallocated                                                     | Garbage Collected                                                                                 |
| Arrays                 | Inline, elements are the actual instances of the value type                                          | Out of line, elements are just references to instances of the reference type residing on the heap |
| Aldel Cost             | Cheap allocation-deallocation                                                                        | Expensive allocation-deallocation                                                                 |
| Memory usage           | Boxed when cast to a reference type or one of the interfaces they implement,                         | No boxing-unboxing                                                                                |
|                        | Unboxed when cast back to value type                                                                 |                                                                                                   |
|                        | (Negative impact because boxes are objects that are allocated on the heap and are garbage-collected) |                                                                                                   |
| Assignments            | Copy entire data                                                                                     | Copy the reference                                                                                |
| Change to an instance  | Does not affect any of its copies                                                                    | Affect all references pointing to the instance                                                    |
| Mutability             | Should be immutable                                                                                  | Mutable                                                                                           |
| Population             | In some situations                                                                                   | Majority of types in a framework should be classes                                                |
| Lifetime               | Short-lived                                                                                          | Long-lived                                                                                        |
| Destructor             | Cannot have                                                                                          | Can have                                                                                          |
| Inheritance            | Only from an interface                                                                               | Full support                                                                                      |
| Polymorphism           | No                                                                                                   | Yes                                                                                               |
| Sealed                 | Yes                                                                                                  | When have sealed keyword                                                                          |
| Constructor            | Can not have explicit parameterless constructors                                                     | Any constructor                                                                                   |
| Null-assignments       | When marked with nullable question mark                                                              | Yes (+ When marked with nullable question mark in C# 8+)                                          |
| Abstract               | No                                                                                                   | When have abstract keyword                                                                        |
| Member Access Modifiers| public, private, internal                                                                            | public, protected, internal, protected internal, private protected                                |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+

1
Questo è in realtà abbastanza splendido: riassunto e informativo. Ricorda di rileggere la risposta almeno una volta: hai scambiato le spiegazioni di struttura e classe in alcune righe, inoltre ci sono alcuni errori di battitura.
Robert Synoradzki,

1
@ensisNoctis Ci scusiamo per quegli errori e grazie per la modifica. Dovrei rileggere le mie risposte 😅
0xaryan

8

Struttura vs classe

Una struttura è un tipo di valore, quindi viene archiviata nello stack, ma una classe è un tipo di riferimento e viene archiviata nell'heap.

Una struttura non supporta l'ereditarietà e il polimorfismo, ma una classe supporta entrambi.

Per impostazione predefinita, tutti i membri della struttura sono pubblici ma i membri della classe sono per impostazione predefinita di natura privata.

Poiché una struttura è un tipo di valore, non è possibile assegnare null a un oggetto struct, ma non è il caso di una classe.


5
Per quanto riguarda "tutti i membri della struttura sono pubblici": se non sbaglio, non è corretto. "Il livello di accesso per i membri della classe e membri della struttura, comprese le classi e le strutture nidificate, è privato per impostazione predefinita." msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

Per aggiungere alle altre risposte, c'è una differenza fondamentale che vale la pena notare ed è il modo in cui i dati vengono archiviati all'interno di array in quanto ciò può avere un effetto importante sulle prestazioni.

  • Con una struttura, l'array contiene l'istanza della struttura
  • Con una classe, l'array contiene un puntatore a un'istanza della classe altrove in memoria

Quindi una serie di strutture assomiglia a questa in memoria

[struct][struct][struct][struct][struct][struct][struct][struct]

Considerando che un array di classi assomiglia a questo

[pointer][pointer][pointer][pointer][pointer][pointer][pointer][pointer]

Con una matrice di classi, i valori che ti interessano non sono memorizzati nella matrice, ma altrove nella memoria.

Per la stragrande maggioranza delle applicazioni questa differenza non ha molta importanza, tuttavia, nel codice ad alte prestazioni ciò influirà sulla localizzazione dei dati all'interno della memoria e avrà un grande impatto sulle prestazioni della cache della CPU. L'uso di classi quando avresti potuto / dovuto usare le strutture aumenterà enormemente il numero di mancati cache nella CPU.

La cosa più lenta di una CPU moderna non è sgretolare i numeri, sta recuperando i dati dalla memoria e un hit della cache L1 è molte volte più veloce della lettura dei dati dalla RAM.

Ecco un po 'di codice che puoi testare. Sulla mia macchina, l'iterazione attraverso l'array class richiede ~ 3 volte più a lungo dell'array struct.

    private struct PerformanceStruct
    {
        public int i1;
        public int i2;
    }

    private class PerformanceClass
    {
        public int i1;
        public int i2;
    }

    private static void DoTest()
    {
        var structArray = new PerformanceStruct[100000000];
        var classArray = new PerformanceClass[structArray.Length];

        for (var i = 0; i < structArray.Length; i++)
        {
            structArray[i] = new PerformanceStruct();
            classArray[i] = new PerformanceClass();
        }

        long total = 0;
        var sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < structArray.Length; i++)
        {
            total += structArray[i].i1 + structArray[i].i2;
        }

        sw.Stop();
        Console.WriteLine($"Struct Time: {sw.ElapsedMilliseconds}");
        sw = new Stopwatch();
        sw.Start();
        for (var loops = 0; loops < 100; loops++)
        for (var i = 0; i < classArray.Length; i++)
        {
            total += classArray[i].i1 + classArray[i].i2;
        }

        Console.WriteLine($"Class Time: {sw.ElapsedMilliseconds}");
    }

-1; "Le strutture sono tipi di valore, quindi memorizzano un valore, le classi sono tipi di riferimento, quindi fanno riferimento a una classe." è poco chiaro e improbabile che abbia senso per chiunque non l'abbia già capito dalle altre risposte qui, e "Con una classe la classe contenente conterrà semplicemente un puntatore alla nuova classe in una diversa area di memoria". confonde le classi con le istanze di classe.
Mark Amery,

@MarkAmery Ho provato a chiarire leggermente. Il punto che stavo davvero cercando di fare era la differenza nel modo in cui gli array funzionano con i tipi di valore e di riferimento e l'effetto che ciò ha sulle prestazioni. Non stavo cercando di spiegare di nuovo quali sono i valori e i tipi di riferimento, poiché ciò viene fatto in molte altre risposte.
Will Calderwood,

7

Giusto per completarlo, c'è un'altra differenza quando si utilizza il Equalsmetodo, che è ereditato da tutte le classi e strutture.

Diciamo che abbiamo una classe e una struttura:

class A{
  public int a, b;
}
struct B{
  public int a, b;
}

e nel metodo Main, abbiamo 4 oggetti.

static void Main{
  A c1 = new A(), c2 = new A();
  c1.a = c1.b = c2.a = c2.b = 1;
  B s1 = new B(), s2 = new B();
  s1.a = s1.b = s2.a = s2.b = 1;
}

Poi:

s1.Equals(s2) // true
s1.Equals(c1) // false
c1.Equals(c2) // false
c1 == c2 // false

Quindi , le strutture sono adatte per oggetti di tipo numerico, come punti (salva le coordinate xey). E le lezioni sono adatte per gli altri. Anche se 2 persone hanno lo stesso nome, altezza, peso ..., sono comunque 2 persone.


6

Bene, per cominciare, una struttura viene passata per valore piuttosto che per riferimento. Le strutture sono buone per strutture di dati relativamente semplici, mentre le classi hanno molta più flessibilità dal punto di vista architettonico attraverso il polimorfismo e l'ereditarietà.

Altri probabilmente possono darti più dettagli di me, ma uso le strutture quando la struttura che sto cercando è semplice.


4

Oltre alla differenza di base dell'identificatore di accesso, e alcuni citati sopra vorrei aggiungere alcune delle principali differenze tra cui alcune delle citate sopra con un esempio di codice con output, che darà un'idea più chiara del riferimento e del valore

le strutture:

  • Sono tipi di valore e non richiedono allocazione di heap.
  • L'allocazione della memoria è diversa ed è memorizzata nello stack
  • Utile per piccole strutture di dati
  • Influisce sulle prestazioni, quando passiamo il valore al metodo, passiamo l'intera struttura dei dati e tutto viene passato allo stack.
  • Il costruttore restituisce semplicemente il valore della struttura stessa (in genere in una posizione temporanea sullo stack) e questo valore viene quindi copiato come necessario
  • Ciascuna delle variabili ha la propria copia dei dati e non è possibile che le operazioni su uno influiscano sull'altro.
  • Non supportano l'ereditarietà specificata dall'utente e ereditano implicitamente dall'oggetto tipo

Classe:

  • Valore del tipo di riferimento
  • Archiviato in Heap
  • Memorizzare un riferimento a un oggetto allocato dinamicamente
  • I costruttori vengono richiamati con il nuovo operatore, ma ciò non alloca memoria sull'heap
  • Più variabili possono avere un riferimento allo stesso oggetto
  • È possibile che le operazioni su una variabile influiscano sull'oggetto a cui fa riferimento l'altra variabile

Esempio di codice

    static void Main(string[] args)
    {
        //Struct
        myStruct objStruct = new myStruct();
        objStruct.x = 10;
        Console.WriteLine("Initial value of Struct Object is: " + objStruct.x);
        Console.WriteLine();
        methodStruct(objStruct);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Struct Object is: " + objStruct.x);
        Console.WriteLine();

        //Class
        myClass objClass = new myClass(10);
        Console.WriteLine("Initial value of Class Object is: " + objClass.x);
        Console.WriteLine();
        methodClass(objClass);
        Console.WriteLine();
        Console.WriteLine("After Method call value of Class Object is: " + objClass.x);
        Console.Read();
    }
    static void methodStruct(myStruct newStruct)
    {
        newStruct.x = 20;
        Console.WriteLine("Inside Struct Method");
        Console.WriteLine("Inside Method value of Struct Object is: " + newStruct.x);
    }
    static void methodClass(myClass newClass)
    {
        newClass.x = 20;
        Console.WriteLine("Inside Class Method");
        Console.WriteLine("Inside Method value of Class Object is: " + newClass.x);
    }
    public struct myStruct
    {
        public int x;
        public myStruct(int xCons)
        {
            this.x = xCons;
        }
    }
    public class myClass
    {
        public int x;
        public myClass(int xCons)
        {
            this.x = xCons;
        }
    }

Produzione

Il valore iniziale di Struct Object è: 10

Metodo Inside Struct Il valore Inside Method dell'oggetto Struct è: 20

Il valore della chiamata After Method di Struct Object è: 10

Il valore iniziale di Class Object è: 10

Metodo Inside Class Il valore Inside Method dell'oggetto Class è: 20

Il valore della chiamata After Method dell'oggetto Class è: 20

Qui puoi vedere chiaramente la differenza tra chiamata per valore e chiamata per riferimento.


4
  1. Gli eventi dichiarati in una classe hanno il loro accesso + = e - = automaticamente bloccato tramite un lucchetto (questo) per renderli thread sicuri (gli eventi statici sono bloccati sul tipo di classe). Gli eventi dichiarati in una struttura non hanno il loro accesso + = e - = automaticamente bloccato. Un blocco (questo) per una struttura non funzionerebbe poiché è possibile bloccare solo un'espressione del tipo di riferimento.

  2. La creazione di un'istanza struct non può causare una garbage collection (a meno che il costruttore non crei direttamente o indirettamente un'istanza del tipo di riferimento) mentre la creazione di un'istanza del tipo di riferimento può causare la garbage collection.

  3. Una struttura ha sempre un costruttore predefinito predefinito incorporato.

    class DefaultConstructor
    {
        static void Eg()
        {
            Direct     yes = new   Direct(); // Always compiles OK
            InDirect maybe = new InDirect(); // Compiles if constructor exists and is accessible
            //...
        }
    }

    Ciò significa che una struttura è sempre istantanea mentre una classe potrebbe non esserlo poiché tutti i suoi costruttori potrebbero essere privati.

    class NonInstantiable
    {
        private NonInstantiable() // OK
        {
        }
    }
    
    struct Direct
    {
        private Direct() // Compile-time error
        {
        }
    }
  4. Una struttura non può avere un distruttore. Un distruttore è solo una sostituzione di un oggetto: finalizzato sotto mentite spoglie, e le strutture, essendo tipi di valore, non sono soggette alla garbage collection.

    struct Direct
    {
        ~Direct() {} // Compile-time error
    }
    class InDirect
    {
        ~InDirect() {} // Compiles OK
    }
    
    And the CIL for ~Indirect() looks like this:
    
    .method family hidebysig virtual instance void
            Finalize() cil managed
    {
      // ...
    } // end of method Indirect::Finalize
  5. Una struttura è implicitamente sigillata, una classe no.
    Una struttura non può essere astratta, una classe può.
    Una struttura non può chiamare: base () nel suo costruttore mentre una classe senza una classe base esplicita può farlo.
    Una struttura non può estendere un'altra classe, una classe può.
    Una struttura non può dichiarare membri protetti (ad esempio campi, tipi nidificati) che una classe può.
    Una struttura non può dichiarare membri di funzioni astratte, una classe astratta può.
    Una struttura non può dichiarare membri di funzioni virtuali, una classe può.
    Una struttura non può dichiarare membri di funzioni sigillate, una classe può.
    Una struttura non può dichiarare i membri della funzione di override, una classe può.
    L'unica eccezione a questa regola è che una struttura può sovrascrivere i metodi virtuali di System.Object, vale a dire, Equals () e GetHashCode () e ToString ().


In quali circostanze si potrebbe usare un evento con una struttura? Posso immaginare che un programma scritto con molta attenzione possa usare eventi con una struttura in un modo che funzioni, ma solo se la struttura non è mai stata copiata o passata per valore, nel qual caso potrebbe anche essere una classe.
Supercat,

@supercat Sì, un evento non statico in una struttura sarebbe molto strano, e sarà utile solo per le strutture mutabili, e l'evento stesso (se si tratta di un evento "simile a un campo") trasforma la struttura in "mutabile" "categoria e introduce anche un campo di tipo di riferimento nella struttura. Gli eventi non statici nelle strutture devono essere malvagi.
Jeppe Stig Nielsen,

@JeppeStigNielsen: L'unico modello che ho potuto vedere dove avrebbe senso per uno struct avere un evento sarebbe se lo scopo dello struct fosse di mantenere un riferimento immutabile a un oggetto di classe per il quale si comportava come un proxy. Tuttavia, gli eventi automatici sarebbero totalmente inutili in uno scenario del genere; invece, gli eventi di iscrizione e annullamento dell'iscrizione dovrebbero essere inoltrati alla classe dietro la struttura. Vorrei che .NET avesse (o renderebbe possibile definire) un tipo di struttura "cache-box" con un campo nascosto di tipo inizialmente nullo Object, che conterrebbe un riferimento a una copia in scatola della struttura.
supercat

1
@JeppeStigNielsen: le strutture sovraperformano le classi in molti scenari di utilizzo del proxy; il problema più grande nell'uso delle strutture è che nei casi in cui la boxe diventa necessaria, spesso finisce per essere rinviata a un circuito interno. Se ci fosse un modo per evitare che le strutture vengano inscatolate ripetutamente , sarebbero meglio delle classi in molti altri scenari di utilizzo.
supercat

4

Come accennato in precedenza: le classi sono di tipo di riferimento mentre le strutture sono tipi di valore con tutte le conseguenze.

Come regola generale, le Linee guida per la progettazione di framework consiglia di utilizzare Struct anziché le classi se:

  • Ha una dimensione dell'istanza inferiore a 16 byte
  • Rappresenta logicamente un singolo valore, simile ai tipi primitivi (int, double, ecc.)
  • È immutabile
  • Non dovrà essere inscatolato frequentemente

3

Esiste un caso interessante di puzzle "class vs struct": la situazione in cui è necessario restituire diversi risultati dal metodo: scegliere quale utilizzare. Se conosci la storia di ValueTuple, sai che ValueTuple (struct) è stato aggiunto perché dovrebbe essere più efficace di Tuple (classe). Ma cosa significa in numeri? Due test: uno è struct / class che ha 2 campi, l'altro con struct / class che ha 8 campi (con dimensione maggiore di 4 - la classe dovrebbe diventare più efficace quindi strutt in termini di tick del processore, ma ovviamente anche il carico GC dovrebbe essere considerato ).

PS Un altro punto di riferimento per il caso specifico "sturct o classe con collezioni" è qui: https://stackoverflow.com/a/45276657/506147

BenchmarkDotNet=v0.10.10, OS=Windows 10 Redstone 2 [1703, Creators Update] (10.0.15063.726)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233540 Hz, Resolution=309.2586 ns, Timer=TSC
.NET Core SDK=2.0.3
  [Host] : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT
  Clr    : .NET Framework 4.7 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.2115.0
  Core   : .NET Core 2.0.3 (Framework 4.6.25815.02), 64bit RyuJIT


            Method |  Job | Runtime |     Mean |     Error |    StdDev |      Min |      Max |   Median | Rank |  Gen 0 | Allocated |
------------------ |----- |-------- |---------:|----------:|----------:|---------:|---------:|---------:|-----:|-------:|----------:|
  TestStructReturn |  Clr |     Clr | 17.57 ns | 0.1960 ns | 0.1834 ns | 17.25 ns | 17.89 ns | 17.55 ns |    4 | 0.0127 |      40 B |
   TestClassReturn |  Clr |     Clr | 21.93 ns | 0.4554 ns | 0.5244 ns | 21.17 ns | 23.26 ns | 21.86 ns |    5 | 0.0229 |      72 B |
 TestStructReturn8 |  Clr |     Clr | 38.99 ns | 0.8302 ns | 1.4097 ns | 37.36 ns | 42.35 ns | 38.50 ns |    8 | 0.0127 |      40 B |
  TestClassReturn8 |  Clr |     Clr | 23.69 ns | 0.5373 ns | 0.6987 ns | 22.70 ns | 25.24 ns | 23.37 ns |    6 | 0.0305 |      96 B |
  TestStructReturn | Core |    Core | 12.28 ns | 0.1882 ns | 0.1760 ns | 11.92 ns | 12.57 ns | 12.30 ns |    1 | 0.0127 |      40 B |
   TestClassReturn | Core |    Core | 15.33 ns | 0.4343 ns | 0.4063 ns | 14.83 ns | 16.44 ns | 15.31 ns |    2 | 0.0229 |      72 B |
 TestStructReturn8 | Core |    Core | 34.11 ns | 0.7089 ns | 1.4954 ns | 31.52 ns | 36.81 ns | 34.03 ns |    7 | 0.0127 |      40 B |
  TestClassReturn8 | Core |    Core | 17.04 ns | 0.2299 ns | 0.2150 ns | 16.68 ns | 17.41 ns | 16.98 ns |    3 | 0.0305 |      96 B |

Test del codice:

using System;
using System.Text;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Attributes.Columns;
using BenchmarkDotNet.Attributes.Exporters;
using BenchmarkDotNet.Attributes.Jobs;
using DashboardCode.Routines.Json;

namespace Benchmark
{
    //[Config(typeof(MyManualConfig))]
    [RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkStructOrClass
    {
        static TestStruct testStruct = new TestStruct();
        static TestClass testClass = new TestClass();
        static TestStruct8 testStruct8 = new TestStruct8();
        static TestClass8 testClass8 = new TestClass8();
        [Benchmark]
        public void TestStructReturn()
        {
            testStruct.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn()
        {
            testClass.TestMethod();
        }


        [Benchmark]
        public void TestStructReturn8()
        {
            testStruct8.TestMethod();
        }

        [Benchmark]
        public void TestClassReturn8()
        {
            testClass8.TestMethod();
        }

        public class TestStruct
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestClass
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance = ++i;
                return x;
            }
        }

        public class TestStruct8
        {
            public int Number = 5;
            public struct StructType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private StructType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private StructType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private StructType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private StructType<int> Method4(int i)
            {
                var x = new StructType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }

        public class TestClass8
        {
            public int Number = 5;
            public class ClassType<T>
            {
                public T Instance1;
                public T Instance2;
                public T Instance3;
                public T Instance4;
                public T Instance5;
                public T Instance6;
                public T Instance7;
                public List<string> List;
            }

            public int TestMethod()
            {
                var s = Method1(1);
                return s.Instance1;
            }

            private ClassType<int> Method1(int i)
            {
                return Method2(++i);
            }

            private ClassType<int> Method2(int i)
            {
                return Method3(++i);
            }

            private ClassType<int> Method3(int i)
            {
                return Method4(++i);
            }

            private ClassType<int> Method4(int i)
            {
                var x = new ClassType<int>();
                x.List = new List<string>();
                x.Instance1 = ++i;
                return x;
            }
        }
    }
}

2

Le strutture sono il valore reale: possono essere vuote ma mai nulle

Questo è vero, tuttavia si noti anche che a partire da .NET 2 le strutture supportano una versione Nullable e C # fornisce dello zucchero sintattico per facilitarne l'uso.

int? value = null;
value  = 1;

1
Tieni presente che questo è solo zucchero sintattico che recita "Nullable <int> value = null; '
Erik van Brakel,

@ErikvanBrakel Questo non è solo zucchero sintattico. Le diverse regole di boxe significano (object)(default(int?)) == nullche non puoi fare con nessun altro tipo di valore, perché qui c'è molto più che zucchero. L'unico zucchero è int?per Nullable<int>.
Jon Hanna,

-1; questo non affronta la domanda su quale sia la differenza tra le strutture e le classi e, come tale, avrebbe dovuto essere un commento sulla risposta a cui stai rispondendo, non una risposta separata. (Anche se forse le norme del sito erano diverse nell'agosto 2008!)
Mark Amery il

1

Ogni variabile o campo di un tipo di valore primitivo o tipo di struttura contiene un'istanza univoca di quel tipo, inclusi tutti i suoi campi (pubblici e privati). Al contrario, variabili o campi di tipi di riferimento possono essere nulli o fare riferimento a un oggetto, memorizzato altrove, al quale può anche esistere un numero qualsiasi di altri riferimenti. I campi di una struttura verranno archiviati nello stesso posto della variabile o del campo di quel tipo di struttura, che può essere nello stack o può far parte di un altro oggetto heap.

La creazione di una variabile o campo di un tipo di valore primitivo lo creerà con un valore predefinito; la creazione di una variabile o di un campo di un tipo di struttura creerà una nuova istanza, creando tutti i campi in essa predefiniti. La creazione di una nuova istanza di un tipo di riferimento inizierà creando tutti i campi in essa contenuti nel modo predefinito e quindi eseguendo il codice aggiuntivo opzionale in base al tipo.

Copiando una variabile o un campo di un tipo primitivo su un altro si copierà il valore. Copiando una variabile o un campo del tipo di struttura su un altro, tutti i campi (pubblici e privati) della prima istanza verranno copiati nella seconda istanza. La copia di una variabile o di un campo di tipo di riferimento in un altro farà sì che quest'ultimo faccia riferimento alla stessa istanza del primo (se presente).

È importante notare che in alcuni linguaggi come il C ++, il comportamento semantico di un tipo è indipendente da come viene archiviato, ma ciò non è vero per .NET. Se un tipo implementa la semantica del valore mutabile, la copia di una variabile di quel tipo in un'altra copia le proprietà della prima in un'altra istanza, a cui fa riferimento la seconda, e l'utilizzo di un membro della seconda per la mutazione causerà la modifica della seconda istanza , ma non il primo. Se un tipo implementa la semantica di riferimento mutabile, la copia di una variabile in un'altra e l'utilizzo di un membro della seconda per mutare l'oggetto influenzerà l'oggetto a cui fa riferimento la prima variabile; i tipi con semantica immutabile non consentono la mutazione, quindi non importa semanticamente se la copia crea una nuova istanza o crea un altro riferimento alla prima.

In .NET, per i tipi di valore è possibile implementare una delle semantiche sopra indicate, purché tutti i loro campi possano fare altrettanto. Un tipo di riferimento, tuttavia, può implementare solo semantica di riferimento mutabile o semantica immutabile; i tipi di valore con campi di tipi di riferimento mutabili sono limitati all'implementazione della semantica di riferimento mutabile o alla strana semantica ibrida.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.