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


Respuestas:


1058

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

Las estructuras son tipos de valor 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 todo el valor del tipo de valor. Para una estructura, eso significa que la variable contiene toda la estructura, con todos sus campos.

Una variable que contiene un tipo de referencia contiene un puntero o una referencia a otro lugar de 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 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:

  • copiando el contenido 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.
  • copiando el contenido de una variable de tipo de referencia en otra variable, copia la referencia, lo que significa que ahora tiene dos referencias a la misma en otro lugar para almacenar 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 en realidad solo está mirando los mismos datos en ambos lugares

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

  • variable: el tipo de valor vive en la pila, el tipo de referencia vive en la pila como un puntero a algún lugar en la memoria del montón donde vive la memoria real (aunque tenga en cuenta la serie de artículos Eric Lipperts: La pila es un detalle de implementación ).
  • class / struct-field: 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.

43
En aras de la completa integridad, debo mencionar que Eric Lippert ha dicho que la pila es un detalle de implementación , cada vez que menciono la pila anterior, tengo en cuenta las publicaciones de Eric.
Lasse V. Karlsen

2
¿Es todo esto válido también para C ++?
Koray Tugay

99
Otra diferencia crucial es el uso. Desde MSDN: "las estructuras se usan típicamente para encapsular un pequeño grupo de variables relacionadas, como coordenadas de rectángulo. Las estructuras también pueden contener constructores, constantes, campos, métodos, propiedades, indexadores, operadores, eventos y tipos anidados, aunque si varios los miembros son obligatorios, debería considerar convertir su tipo en una clase ".
thewpfguy

44
@KorayTugay No, no lo es.
ZoomIn

99
@KorayTugay en C ++ struct y class son absolutamente equivalentes, excepto por una cosa: restricción de acceso predeterminada (la clase tiene private por defecto, struct tiene public)
berkus

207

Un breve resumen de cada uno:

Solo clases:

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

Solo estructuras:

  • No se puede admitir la herencia.
  • Son tipos de valor
  • Se pasan por valor (como enteros)
  • No puede tener una referencia nula (a menos que se utilice 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 se usan típicamente para contener algunas variables que tienen alguna relación lógica?
  • Puede contener métodos y eventos.
  • Puede soportar interfaces

16
Hay algunas partes de esta respuesta que no son del todo correctas. Las clases no siempre van en el montón, y las estructuras no siempre van en la pila. Las excepciones actuales incluyen campos de estructura en una clase, variables capturadas en métodos anónimos y expresiones lambda, bloques iteradores y los valores encuadrados ya mencionados. Pero la asignación de pila frente a montón es un detalle de implementación y puede estar sujeta a cambios. Eric lippart discute esto aquí . He votado en contra, pero felizmente lo eliminaré si actualizas.
Simon P Stevens

1
struct no admite la herencia de otros stucts / clases, pero PUEDE implementar una interfaz en una struct.
thewpfguy

2
Es posible que desee aclarar lo que quiere decir cuando afirma que las estructuras "no tienen una sobrecarga de memoria por nueva instancia" . Mi primera interpretación fue que estabas afirmando, obviamente absurdamente, que las estructuras usan memoria cero. Entonces pensé que tal vez estás tratando de decir que una estructura, a diferencia de una clase, requiere exactamente tanta memoria como la suma de sus campos miembros, y nada más. Pero luego busqué en Google c# struct memory overheady encontré esta respuesta de Hans Passant que dice que no, ese tampoco es el caso. Entonces, ¿ qué quiere decir?
Mark Amery

44
@ MarkAmery Tuve la misma reacción inicial que tu id a la expresión "sin sobrecarga de memoria", pero creo que el OP se refiere al hecho de que las instancias de classmemoria gestionada (manejada por el recolector de basura), mientras que las instancias de structno .
Hutch

1
"Las estructuras se pasan por valor (como enteros)" es falso: todas las variables se pasan por valor, también el tipo de referencia. Si desea pasar una variable por referencia, debe usar la palabra clave "ref". jonskeet.uk/csharp/parameters.html#ref
Marco Staffoli

41

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

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

Cuando pasa 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 pueden ser nada: la referencia puede apuntar a un valor nulo.

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


@ T.Todua, sí, hay mejores respuestas arriba, que voté y elegí como la respuesta después de proporcionar esta, esto es de la versión beta temprana de SO cuando todavía estábamos descubriendo las reglas.
Keith

1
No sé si me entendió correctamente, realmente voté / acepté su respuesta (a diferencia de las respuestas anteriores), porque ha tenido buenos ejemplos (no solo una explicación teórica, en oposición a la respuesta anterior, que solo tenía explicaciones teóricas sin ejemplos )
T.Todua

24

Diferencia entre estructuras y clases:

  • Las estructuras son de tipo de valor, mientras que las clases son de tipo de referencia .
  • Las estructuras se almacenan en la pila, 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 perder el alcance, mientras que el tipo de referencia solo destruye la variable después de perder el alcance. El objeto luego es destruido por el recolector de basura.
  • Cuando copia estructura en otra estructura, se crea una nueva copia de esa estructura modificada de una estructura que no afectará el valor de la otra estructura.
  • Cuando copia una clase en otra clase, solo copia la variable de referencia.
  • Tanto la variable de referencia apunta 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.
  • Las estructuras no pueden tener constructores explícitos sin parámetros, mientras que una clase puede estructuras no admite la herencia, pero las clases sí. Ambos admiten herencia de una interfaz.
  • Las estructuras son de tipo sellado .

21

De la elección de Microsoft entre clase y estructura ...

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 hacen que sea más apropiado usar estructuras.

CONSIDERA una estructura en lugar de una clase:

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

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

  • Lógicamente representa un valor único, similar a los tipos primitivos (int, double, etc.).
  • Tiene un tamaño de instancia inferior a 16 bytes.
  • Es inmutable. (no puede ser cambiado)
  • No tendrá que ser encuadrado con frecuencia.

19

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

  1. Las estructuras no pueden tener un constructor explícito sin parámetros, 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 se pueden implementar desde una interfaz).

Si está buscando un video que explique todas las diferencias, puede consultar la Parte 29 - Tutorial de C # - Diferencia entre clases y estructuras en C # .


44
Mucho más significativo que el hecho de que los lenguajes .net generalmente no permitirán que una estructura defina un constructor sin parámetros (la decisión de si el compilador del lenguaje lo permite o no) es el hecho de que las estructuras pueden existir y quedar expuestas. al mundo exterior sin que se haya ejecutado ningún tipo de constructor (incluso cuando se define un constructor sin parámetros). La razón por la que los lenguajes .net generalmente prohíben constructores sin parámetros para estructuras es para evitar la confusión que resultaría de que tales constructores se ejecuten a veces y otras no.
supercat

15

Las instancias de 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. Al pasar un objeto a un método, se pasa una copia de la referencia, no el objeto en sí.

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

Los nombres de C ++ / CLI mejoran la distinción: "clase de referencia" 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" tal como las usa C # son simplemente algo que debe aprenderse.


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
Esto es realmente espléndido: resumido e informativo. Recuerde recordar leer su respuesta al menos una vez: intercambió explicaciones de estructura y clase en algunas filas, también hay algunos errores tipográficos.
Robert Synoradzki el

1
@ensisNoctis Perdón por esos errores y gracias por la edición. Debería releer mis respuestas 😅
0xaryan

8

Estructura vs clase

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

Una estructura no admite la herencia y el polimorfismo, pero una clase admite ambos.

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 de estructura, pero no es el caso de una clase.


55
Con respecto a "todos los miembros de la estructura son públicos": si no me equivoco, eso es incorrecto. "El nivel de acceso para los miembros de la clase y los miembros de la estructura, incluidas las clases y estructuras anidadas, es privado de forma predeterminada". msdn.microsoft.com/en-us/library/ms173121.aspx
Nate Cook

8

Para agregar a las otras respuestas, hay una diferencia fundamental que vale la pena señalar, y es cómo se almacenan los datos dentro de las matrices, ya que esto puede tener un efecto importante en el rendimiento.

  • Con una estructura, la matriz contiene la instancia de la estructura
  • Con una clase, la matriz contiene un puntero a una instancia de la clase en otra parte de la memoria

Entonces, una matriz de estructuras se ve así en la memoria

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

Mientras que una variedad de clases se ve así

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

Con una matriz de clases, los valores que le interesan no se almacenan dentro de la matriz, sino en otra parte de la memoria.

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

Lo más lento que hace una CPU moderna es no acumular números, está obteniendo datos de la memoria y un acierto de caché L1 es muchas veces más rápido que leer datos de la RAM.

Aquí hay un código que puede probar. En mi máquina, iterar a través de la matriz de clase tarda ~ 3 veces más que la matriz de estructura.

    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; "Las estructuras son tipos de valor, por lo que almacenan un valor, las clases son tipos de referencia, por lo que hacen referencia a una clase". no está claro y es poco probable que tenga sentido para alguien que aún no lo haya entendido de las otras respuestas aquí, y "Con una clase, la clase que contiene contendrá un puntero a la nueva clase en un área diferente de la memoria". confunde clases con instancias de clase.
Mark Amery

@ MarkAmery He tratado de aclarar un poco. El punto que realmente estaba tratando de hacer fue la diferencia en la forma en que las matrices funcionan con valores y tipos de referencia y el efecto que esto tiene en el rendimiento. No estaba tratando de volver a explicar qué tipo de valor y referencia son, ya que esto se hace en muchas otras respuestas.
Will Calderwood

7

Solo para completarlo, hay otra diferencia al usar el Equalsmétodo, 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 Main, 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

Entonces , las estructuras son adecuadas para objetos numéricos, como puntos (guardar coordenadas x e y). Y las clases son adecuadas para otros. Incluso si 2 personas tienen el mismo nombre, altura, peso ..., siguen siendo 2 personas.


6

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 el punto de vista arquitectónico a través del polimorfismo y la herencia.

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


4

Además de la diferencia básica del especificador de acceso, y los pocos 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

Estructuras:

  • Son tipos de valor y no requieren asignación de montón.
  • La asignación de memoria es diferente y se almacena en la pila.
  • Útil para pequeñas estructuras de datos.
  • Afecta el rendimiento, cuando pasamos valor al método, pasamos toda la estructura de datos y todo se pasa a la pila.
  • El constructor simplemente devuelve el valor de estructura en sí (generalmente en una ubicación temporal en la pila), y este valor 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 admiten la herencia especificada por el usuario, y heredan implícitamente del objeto de tipo

Clase:

  • Valor de tipo de referencia
  • Almacenado en el montón
  • Almacenar una referencia a un objeto asignado dinámicamente
  • 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 del objeto Struct es: 10

Método de estructura interna El valor del método interno del objeto de estructura es: 20

El valor de llamada después del método del objeto Struct es: 10

El valor inicial del objeto de clase es: 10

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

El valor de llamada después del método del objeto de clase es: 20

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


4
  1. Los eventos declarados en una clase tienen su acceso + = y - = bloqueado automáticamente a través de un bloqueo (esto) para que sean seguros para subprocesos (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 bloqueo (esto) para una estructura no funcionaría ya que solo puede bloquear una expresión de tipo de referencia.

  2. Crear una instancia de estructura 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 crear una instancia de tipo de referencia puede causar una recolección de basura.

  3. Una estructura siempre tiene un constructor público predeterminado 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. Finalizar disfrazado, y las estructuras, que son tipos de valor, no están sujetas a la recolección de basura.

    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á sellada implícitamente, una clase no.
    Una estructura no puede ser abstracta, una clase sí.
    Una estructura no puede llamar a: base () en su constructor, mientras que una clase sin una clase base explícita sí puede.
    Una estructura no puede extender otra clase, una clase puede.
    Una estructura no puede declarar miembros protegidos (por ejemplo, campos, tipos anidados) que una clase puede.
    Una estructura no puede declarar miembros de funciones abstractas, una clase abstracta sí.
    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 miembros de la función de anulació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 ().


¿En qué circunstancias se usaría un evento con una estructura? Me imagino que un programa cuidadosamente escrito podría usar eventos con una estructura de una manera que funcionaría, pero solo si la estructura nunca fue copiada o pasada por valor, en cuyo caso podría ser una clase.
supercat

@supercat Sí, un evento no estático en una estructura sería muy extraño, y será útil solo para estructuras mutables, y el evento en sí (si es un evento "similar a un campo") convierte la estructura en "mutable "y también introduce un campo de tipo de referencia en la estructura. Los eventos no estáticos en las estructuras deben ser malvados.
Jeppe Stig Nielsen

@JeppeStigNielsen: El único patrón que podría ver donde tendría sentido para una estructura tener un evento sería si el propósito de la estructura fuera mantener una referencia inmutable a un objeto de clase para el cual se comportó como un proxy. Sin embargo, los eventos automáticos serían totalmente inútiles en tal escenario; en cambio, los eventos de suscripción y cancelación de suscripción tendrían que transmitirse a la clase detrás de la estructura. Desearía que .NET tuviera (o haría posible definir) un tipo de estructura 'caja de caché' con un campo de tipo oculto inicialmente nulo Object, que contendría una referencia a una copia en caja de la estructura.
supercat

1
@JeppeStigNielsen: estructura las clases de rendimiento superior en muchos escenarios de uso de proxy; El mayor problema con el uso de estructuras es que en los casos en que el boxeo termina siendo necesario, a menudo termina siendo diferido a un bucle interno. Si hubiera una manera de evitar que las estructuras se encuadren repetidamente , serían mejores que las clases en muchos más escenarios de uso.
supercat

4

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

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

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

3

Hay un caso interesante de rompecabezas "clase vs estructura": situación en la que necesita devolver varios resultados del método: elija cuál usar. Si conoce la historia de ValueTuple, sabe que ValueTuple (struct) se agregó 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 una dimensión de más de 4 - la clase debería ser más efectiva que la estructura en términos de ticks del procesador, pero por supuesto también se debe considerar la carga de GC )

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

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

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

int? value = null;
value  = 1;

1
Tenga en cuenta que esto es solo azúcar sintáctico que dice 'Nullable <int> value = null;'
Erik van Brakel el

@ErikvanBrakel Eso no es solo azúcar sintáctico. Las diferentes reglas de boxeo significan (object)(default(int?)) == nullque no se puede hacer con ningún otro tipo de valor, porque aquí hay más que solo azúcar. El único azúcar es int?para Nullable<int>.
Jon Hanna

-1; Esto no aborda la cuestión de cuál es la diferencia entre estructuras y clases, y como tal, debería haber sido un comentario sobre la respuesta a la que está respondiendo, no una respuesta separada. (¡Aunque quizás las normas del sitio eran diferentes en agosto de 2008!)
Mark Amery

1

Cada variable o campo de un tipo de valor primitivo o tipo de estructura contiene una instancia única de ese tipo, incluidos todos sus campos (públicos y privados). Por el contrario, las variables o los campos de los tipos de referencia pueden ser nulos, o pueden referirse a un objeto, almacenado en otro lugar, al que también puede existir cualquier número 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 pila o puede ser parte de otro objeto de montón.

Crear 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 de la manera predeterminada. La creación de una nueva instancia de un tipo de referencia comenzará creando todos los campos allí de la manera predeterminada, y luego ejecutará un 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 otra copiará todos los campos (públicos y privados) de la primera instancia a la última instancia. Copiar una variable o campo de tipo de referencia a otra hará que esta última haga referencia a la misma instancia que la primera (si la hay).

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 valor mutable, al copiar una variable de ese tipo a otra, se copian las propiedades de la primera a otra instancia, a las que se refiere la segunda, y el uso de un miembro de la segunda para mutarlo hará que se cambie esa segunda instancia. , pero no el primero. Si un tipo implementa una semántica de referencia mutable, copiar una variable a otra y usar un miembro de la segunda para mutar el objeto afectará al objeto mencionado por 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 valor 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 valores con campos de tipos de referencia mutables se limitan a implementar semántica de referencia mutable o semántica híbrida extraña.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.