Risposte:
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:
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:
Quando dichiari variabili o campi, ecco come differiscono i due tipi:
Un breve riassunto di ciascuno:
Solo lezioni:
Solo strutture:
Sia classi che strutture:
c# struct memory overhead
e ho trovato questa risposta di Hans Passant che dice che no, non è nemmeno il caso. Così che cosa vuoi dire?
class
sono memoria gestita (gestita dal garbage collector), mentre le istanze di struct
non lo sono .
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".
Differenza tra strutture e classi:
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.
Oltre a tutte le differenze descritte nelle altre risposte:
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 # .
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.
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
| | 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 |
+------------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+
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.
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.
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}");
}
Giusto per completarlo, c'è un'altra differenza quando si utilizza il Equals
metodo, 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.
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.
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:
Classe:
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.
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.
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.
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
{
}
}
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
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 ().
Object
, che conterrebbe un riferimento a una copia in scatola della struttura.
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:
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;
}
}
}
}
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;
(object)(default(int?)) == null
che non puoi fare con nessun altro tipo di valore, perché qui c'è molto più che zucchero. L'unico zucchero è int?
per Nullable<int>
.
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.