¿Cuál es la diferencia entre struct y clase en .NET?

¿Cuál es la diferencia entre struct y clase en .NET?

En .NET, hay dos categorías de tipos, tipos de referencia y tipos de valores .

Las estructuras son tipos de valores y las clases son tipos de referencia .

La diferencia general es que un tipo de referencia vive en el montón, y un tipo de valor vive en línea, es decir, donde sea que esté definida su variable o campo.

Una variable que contiene un tipo de valor contiene el valor del tipo de valor completo. Para una estructura, eso significa que la variable contiene la estructura completa, con todos sus campos.

Una variable que contiene un tipo de referencia contiene un puntero o una referencia a otro lugar en la memoria donde reside el valor real.

Esto tiene un beneficio, para empezar:

  • los tipos de valor siempre contienen un valor
  • los tipos de referencia pueden contener una referencia nula , lo que significa que no se refieren a nada en absoluto en este momento

Internamente, los tipos de referencia se implementan como punteros, y sabiendo eso, y sabiendo cómo funciona la asignación de variables, existen otros patrones de comportamiento:

  • al copiar los contenidos de una variable de tipo de valor en otra variable, copia todo el contenido en la nueva variable, haciendo que los dos sean distintos. En otras palabras, después de la copia, los cambios en uno no afectarán al otro
  • al copiar el contenido de una variable de tipo de referencia en otra variable, copia la referencia, lo que significa que ahora tiene dos referencias al mismo almacenamiento en otro lugar de los datos reales. En otras palabras, después de la copia, el cambio de los datos en una referencia también afectará a la otra, pero solo porque realmente solo estás viendo los mismos datos en ambos lugares

Cuando declaras variables o campos, así es como difieren los dos tipos:

  • variable: el tipo de valor vive en la stack, el tipo de referencia vive en la stack como un puntero a algún lugar de la memoria del montón donde vive la memoria real (aunque tenga en cuenta la serie de artículos de Eric Lipperts: La stack es un detalle de implementación ).
  • clase / struct-campo: el tipo de valor vive completamente dentro del tipo, el tipo de referencia vive dentro del tipo como un puntero a algún lugar en la memoria del montón donde vive la memoria real.

Un breve resumen de cada uno:

Solo clases

  • Puede admitir herencia
  • Son tipos de referencia (puntero)
  • La referencia puede ser nula
  • Tener sobrecarga de memoria por nueva instancia

Structs Only:

  • No se puede admitir la herencia
  • Son tipos de valores
  • Se pasan por valor (como enteros)
  • No puede tener una referencia nula (a menos que se use Nullable)
  • No tiene una sobrecarga de memoria por nueva instancia, a menos que esté ‘en caja’

Ambas clases y estructuras:

  • ¿Los tipos de datos compuestos suelen usarse para contener algunas variables que tienen alguna relación lógica?
  • Puede contener métodos y eventos
  • Puede soportar interfaces

En .NET, las declaraciones de estructura y clase diferencian entre tipos de referencia y tipos de valores.

Cuando pasas por un tipo de referencia, solo hay uno realmente almacenado. Todo el código que accede a la instancia está accediendo al mismo.

Cuando pasa alrededor de un tipo de valor, cada uno es una copia. Todo el código está trabajando en su propia copia.

Esto se puede mostrar con un ejemplo:

 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. 

Para una clase esto sería diferente

 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. 

Las clases no pueden ser nada; la referencia puede indicar un nulo.

Las estructuras son el valor real: pueden estar vacías pero nunca son nulas. Por esta razón, las estructuras siempre tienen un constructor predeterminado sin parámetros: necesitan un ‘valor inicial’.

Además de todas las diferencias descritas en las otras respuestas:

  1. Las estructuras no pueden tener un constructor sin parámetros explícito, mientras que una clase puede
  2. Las estructuras no pueden tener destructores , mientras que una clase puede
  3. Las estructuras no pueden heredar de otra estructura o clase mientras que una clase puede heredar de otra clase. (Tanto las estructuras como las clases pueden implementarse desde una interfaz).

Si buscas un video que explique todas las diferencias, puedes consultar la Parte 29 – Tutorial de C # – Diferencia entre las clases y las estructuras en C # .

Las instancias de las clases se almacenan en el montón administrado. Todas las variables ‘que contienen’ una instancia son simplemente una referencia a la instancia en el montón. Pasar un objeto a un método da como resultado una copia de la referencia que se pasa, no el objeto en sí.

Las estructuras (técnicamente, los tipos de valor) se almacenan donde sea que se usen, al igual que un tipo primitivo. El contenido puede ser copiado por el tiempo de ejecución en cualquier momento y sin invocar un copiador-constructor personalizado. Pasar un tipo de valor a un método implica copiar todo el valor, nuevamente sin invocar ningún código personalizable.

La distinción se mejora con los nombres de C ++ / CLI: “clase ref” es una clase como se describe primero, “clase de valor” es una clase como se describe en segundo lugar. Las palabras clave “clase” y “estructura” utilizadas por C # son simplemente algo que debe aprenderse.

De Elegir entre clase y estructura de Microsoft …

Como regla general, la mayoría de los tipos en un marco deberían ser clases. Sin embargo, hay algunas situaciones en las que las características de un tipo de valor lo hacen más apropiado para usar estructuras.

CONSIDERA una estructura en lugar de una clase:

  • Si las instancias del tipo son pequeñas y, por lo general, de corta duración o están comúnmente integradas en otros objetos.

X EVITAR una estructura a menos que el tipo tenga todas las características siguientes:

  • Lógicamente representa un único valor, similar a los tipos primitivos (int, double, etc.).
  • Tiene un tamaño de instancia de menos de 16 bytes.
  • Es inmutable (no puede ser cambiado)
  • No será necesario encasillarlo frecuentemente.

Diferencia entre puntales y clases –

  • Las estructuras son de tipo de valor mientras que las clases son de tipo de referencia .
  • Las estructuras se almacenan en la stack mientras que las clases se almacenan en el montón .
  • Los tipos de valor mantienen su valor en la memoria donde se declaran, pero el tipo de referencia contiene una referencia a la memoria de un objeto.
  • Los tipos de valor se destruyen inmediatamente después de que se pierde el scope, mientras que el tipo de referencia solo destruye la variable después de que se pierde el scope. El objeto es destruido más tarde por el recolector de basura.
  • Cuando copie struct en otra estructura, se creará una nueva copia de esa estructura. La modificación de una estructura no afectará el valor de la otra estructura.
  • Cuando copia una clase en otra clase, solo copia la variable de referencia.
  • Ambas, la variable de referencia apuntan al mismo objeto en el montón. El cambio a una variable afectará a la otra variable de referencia.
  • Las estructuras no pueden tener destructores , pero las clases pueden tener destructores.
  • Structs no puede tener constructores explícitos sin parámetros, mientras que una clase puede estructurar no admite herencia, pero las clases sí. Ambos soportan la herencia desde una interfaz.
  • Las estructuras son de tipo sellado .

Estructura vs clase

Una estructura es un tipo de valor por lo que se almacena en la stack, pero una clase es un tipo de referencia y se almacena en el montón.

Una estructura no admite herencia y polymorphism, pero una clase admite ambas.

Por defecto, todos los miembros de la estructura son públicos, pero los miembros de la clase son de naturaleza privada por defecto.

Como una estructura es un tipo de valor, no podemos asignar nulo a un objeto struct, pero no es el caso para una clase.

Bueno, para empezar, una estructura se pasa por valor en lugar de por referencia. Las estructuras son buenas para estructuras de datos relativamente simples, mientras que las clases tienen mucha más flexibilidad desde un punto de vista arquitectónico a través del polymorphism y la herencia.

Otros probablemente le den más detalles que yo, pero yo uso estructuras cuando la estructura que busco es simple.

Solo para completarlo, hay otra diferencia al usar el método Equals , que es heredado por todas las clases y estructuras.

Digamos que tenemos una clase y una estructura:

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

y en el método Principal, tenemos 4 objetos.

 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; } 

Entonces:

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

Por lo tanto , las estructuras son adecuadas para objetos numéricos, como puntos (guarde las coordenadas xey). Y las clases son adecuadas para otros. Incluso si 2 personas tienen el mismo nombre, altura, peso …, todavía son 2 personas.

Además de la diferencia básica de especificador de acceso, y algunos mencionados anteriormente, me gustaría agregar algunas de las principales diferencias, incluidas algunas de las mencionadas anteriormente, con una muestra de código con salida, que dará una idea más clara de la referencia y el valor

Structs:

  • Son tipos de valor y no requieren asignación de montón.
  • La asignación de memoria es diferente y se almacena en la stack
  • Útil para estructuras de datos pequeñas
  • Afectar el rendimiento, cuando pasamos el valor al método, pasamos toda la estructura de datos y todo se pasa a la stack.
  • Constructor simplemente devuelve el valor de la estructura en sí (generalmente en una ubicación temporal en la stack), y este valor luego se copia según sea necesario
  • Las variables tienen cada una su propia copia de los datos, y no es posible que las operaciones en una afecten a la otra.
  • No es compatible con la herencia especificada por el usuario, y heredan implícitamente del tipo de objeto

Clase:

  • Valor del tipo de referencia
  • Almacenado en Heap
  • Almacenar una referencia a un objeto dinámicamente asignado
  • Los constructores se invocan con el nuevo operador, pero eso no asigna memoria en el montón
  • Varias variables pueden tener una referencia al mismo objeto
  • Es posible que las operaciones en una variable afecten al objeto al que hace referencia la otra variable

Muestra de código

  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; } } 

Salida

El valor inicial de Struct Object es: 10

Método de la estructura interna El valor del método interno de Struct Object es: 20

Después de que el valor de llamada del Método de Struct Object sea: 10

El valor inicial del objeto de clase es: 10

Método de la clase interna El valor del método interior del objeto de la clase es: 20

Después de que el valor de llamada al método del objeto de clase sea: 20

Aquí puede ver claramente la diferencia entre llamada por valor y llamada por referencia.

Como se mencionó anteriormente: las clases son de tipo de referencia, mientras que las estructuras son tipos de valores con todas las consecuencias.

Como un pulgar de la regla, las Pautas de diseño de marcos recomiendan usar Structs en lugar de clases si:

  • Tiene un tamaño de instancia de menos de 16 bytes
  • Lógicamente representa un único valor, similar a los tipos primitivos (int, double, etc.)
  • Es inmutable
  • No tendrá que estar en caja con frecuencia

Para agregar a las otras respuestas, hay una diferencia fundamental que vale la pena señalar, y así es como se almacena en la memoria. Esto puede tener un efecto importante en el rendimiento de las matrices. Las estructuras son tipos de valores, por lo que almacenan un valor en el área de memoria a la que apuntan, las clases son tipos de referencia, por lo que hacen referencia a una clase en el área de memoria a la que apuntan, el valor real se almacena en otro lugar.

  • Con una estructura, la memoria se asigna dentro de la clase contenedora para almacenar los datos.
  • Con una clase, la clase contenedora contendrá un puntero a la nueva clase en un área de memoria diferente.

Esto también es cierto con las matrices, por lo que una serie de estructuras se parece a esto en la memoria

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

Donde como una matriz de clases se ve así

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

Los valores reales que le interesan no se almacenan realmente en la matriz, sino en otra parte de la memoria.

Para la gran mayoría de las aplicaciones, esta diferencia en realidad no importa, sin embargo, en el código de alto rendimiento, esto afectará a la ubicación de los datos en la memoria y tendrá un gran impacto en el rendimiento de la memoria caché de la CPU. El uso de clases cuando podría / debería haber utilizado estructuras boostá enormemente el número de fallas de caché en la CPU.

Lo más lento que hace una CPU moderna no son los números crujientes, es recuperar datos de la memoria, y un golpe de caché L1 es mucho más rápido que leer datos de la RAM.

  1. Los eventos declarados en una clase tienen su acceso + = y – = bloqueado automáticamente a través de un locking (esto) para que sean segmentados (los eventos estáticos están bloqueados en el tipo de la clase). Los eventos declarados en una estructura no tienen su acceso + = y – = bloqueado automáticamente. Un locking (esto) para una estructura no funcionaría, ya que solo puede bloquear una expresión de tipo de referencia.

  2. La creación de una instancia de struct no puede causar una recolección de basura (a menos que el constructor cree directa o indirectamente una instancia de tipo de referencia) mientras que la creación de una instancia de tipo de referencia puede causar la recolección de basura.

  3. Una estructura siempre tiene un constructor predeterminado público incorporado.

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

    Esto significa que una estructura siempre es instanciable, mientras que una clase podría no serlo ya que todos sus constructores podrían ser privados.

     class NonInstantiable { private NonInstantiable() // OK { } } struct Direct { private Direct() // Compile-time error { } } 
  4. Una estructura no puede tener un destructor. Un destructor es solo una anulación del objeto. Finalice en forma encubierta, y las estructuras, al ser tipos de valor, no están sujetas a la recolección de elementos no utilizados.

     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 estructura está implícitamente sellada, una clase no.
    Una estructura no puede ser abstracta, una clase puede.
    Una estructura no puede invocar: base () en su constructor, mientras que una clase sin clase base explícita puede hacerlo.
    Una estructura no puede extender otra clase, una clase puede.
    Una estructura no puede declarar miembros protegidos (por ejemplo, campos, tipos nesteds) que una clase puede.
    Una estructura no puede declarar miembros de función abstracta, una clase abstracta puede.
    Una estructura no puede declarar miembros de funciones virtuales, una clase puede.
    Una estructura no puede declarar miembros de función sellados, una clase puede.
    Una estructura no puede declarar anular los miembros de la función, una clase puede.
    La única excepción a esta regla es que una estructura puede anular los métodos virtuales de System.Object, viz, Equals () y GetHashCode () y ToString ().

Las estructuras son el valor real: pueden estar vacías pero nunca son nulas.

Esto es cierto, sin embargo, también tenga en cuenta que a partir de las estructuras .NET 2 admite una versión anulable y C # suministra algo de azúcar sintáctico para que sea más fácil de usar.

 int? value = null; value = 1; 

Hay un caso interesante de acertijo “clase vs estructura”: situación en la que debe devolver varios resultados del método: elija cuál usar. Si conoce la historia de ValueTuple, sabe que se agregó ValueTuple (struct) porque debería ser más efectivo que Tuple (clase). Pero, ¿qué significa en números? Dos pruebas: una es struct / class que tiene 2 campos, otra con struct / class que tiene 8 campos (con dimensión más que 4). La clase debería ser más efectiva que struct en términos de ticks del procesador, pero por supuesto la carga del GC también debe considerarse )

PD Otro punto de referencia para el caso específico ‘sturct o clase con colecciones’ está allí: 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 | 

Prueba de código:

 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 { public T Instance; public List List; } public int TestMethod() { var s = Method1(1); return s.Instance; } private StructType Method1(int i) { return Method2(++i); } private StructType Method2(int i) { return Method3(++i); } private StructType Method3(int i) { return Method4(++i); } private StructType Method4(int i) { var x = new StructType(); x.List = new List(); x.Instance = ++i; return x; } } public class TestClass { public int Number = 5; public class ClassType { public T Instance; public List List; } public int TestMethod() { var s = Method1(1); return s.Instance; } private ClassType Method1(int i) { return Method2(++i); } private ClassType Method2(int i) { return Method3(++i); } private ClassType Method3(int i) { return Method4(++i); } private ClassType Method4(int i) { var x = new ClassType(); x.List = new List(); x.Instance = ++i; return x; } } public class TestStruct8 { public int Number = 5; public struct StructType { public T Instance1; public T Instance2; public T Instance3; public T Instance4; public T Instance5; public T Instance6; public T Instance7; public List List; } public int TestMethod() { var s = Method1(1); return s.Instance1; } private StructType Method1(int i) { return Method2(++i); } private StructType Method2(int i) { return Method3(++i); } private StructType Method3(int i) { return Method4(++i); } private StructType Method4(int i) { var x = new StructType(); x.List = new List(); x.Instance1 = ++i; return x; } } public class TestClass8 { public int Number = 5; public class ClassType { public T Instance1; public T Instance2; public T Instance3; public T Instance4; public T Instance5; public T Instance6; public T Instance7; public List List; } public int TestMethod() { var s = Method1(1); return s.Instance1; } private ClassType Method1(int i) { return Method2(++i); } private ClassType Method2(int i) { return Method3(++i); } private ClassType Method3(int i) { return Method4(++i); } private ClassType Method4(int i) { var x = new ClassType(); x.List = new List(); x.Instance1 = ++i; return x; } } } } 

Cada variable o campo de un tipo de estructura o tipo de valor primitivo contiene una instancia única de ese tipo, incluidos todos sus campos (público y privado). Por el contrario, las variables o los tipos de campos de referencia pueden contener nulos, o pueden referirse a un objeto, almacenado en otro lugar, al que puede existir cualquier cantidad de otras referencias. Los campos de una estructura se almacenarán en el mismo lugar que la variable o el campo de ese tipo de estructura, que puede estar en la stack o ser parte de otro objeto de stack.

La creación de una variable o campo de un tipo de valor primitivo lo creará con un valor predeterminado; la creación de una variable o campo de un tipo de estructura creará una nueva instancia, creando todos los campos en la misma de manera predeterminada. La creación de una nueva instancia de un tipo de referencia comenzará creando todos los campos de la manera predeterminada y luego ejecutando código adicional opcional según el tipo.

Copiar una variable o campo de un tipo primitivo a otro copiará el valor. Copiar una variable o campo de tipo de estructura a otro copiará todos los campos (públicos y privados) de la instancia anterior a la última instancia. Copiar una variable o campo de tipo de referencia hará que este último se refiera a la misma instancia que el anterior (si corresponde).

Es importante tener en cuenta que en algunos lenguajes como C ++, el comportamiento semántico de un tipo es independiente de cómo se almacena, pero eso no es cierto para .NET. Si un tipo implementa una semántica de valores mutables, al copiar una variable de ese tipo a otra, se copian las propiedades de la primera a otra instancia, a la que hace referencia el segundo, y al usar un miembro del segundo para mutarla se cambiará esa segunda instancia. , pero no el primero. Si un tipo implementa semántica de referencia mutable, copiar una variable a otra y usar un miembro del segundo para mutar el objeto afectará al objeto al que hace referencia la primera variable; los tipos con semántica inmutable no permiten la mutación, por lo que no importa semánticamente si la copia crea una nueva instancia o crea otra referencia a la primera.

En .NET, es posible que los tipos de valores implementen cualquiera de las semánticas anteriores, siempre que todos sus campos puedan hacer lo mismo. Sin embargo, un tipo de referencia solo puede implementar semántica de referencia mutable o semántica inmutable; los tipos de valor con campos de tipos de referencia mutables se limitan a implementar semántica de referencia mutable o semántica híbrida extraña.

 +-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+ | | 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 | Mutable | Should be immutable | | Population | Majority of types in a framework should be classes | In some situations | | Lifetime | Long-lived | Short-lived | | Destructor | Can't have | Can have | | Inheritence | Only from an interdace | Full-support | | Polymotphism | 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 | | Abstract | No | When have abstract keyword | | Access Modifiers | public, private, internal | public, protected, internal, protected internal, private protected | +-----------------------+------------------------------------------------------------------------------------------------------+---------------------------------------------------------------------------------------------------+