¿Cuándo debo usar una estructura en lugar de una clase en C #?


1391

¿Cuándo deberías usar struct y no class en C #? Mi modelo conceptual es que las estructuras se usan en momentos en que el elemento es simplemente una colección de tipos de valor . Una forma de mantenerlos lógicamente todos juntos en un todo coherente.

Me encontré con estas reglas aquí :

  • Una estructura debe representar un solo valor.
  • Una estructura debe tener una huella de memoria inferior a 16 bytes.
  • Una estructura no debe cambiarse después de la creación.

¿Estas reglas funcionan? ¿Qué significa una estructura semánticamente?


248
System.Drawing.Rectangleviola las tres reglas.
ChrisW

44
hay bastantes juegos comerciales escritos en C #, el punto es que se usan para código optimizado
BlackTigerX

25
Las estructuras proporcionan un mejor rendimiento cuando tiene pequeñas colecciones de tipos de valor que desea agrupar. Esto sucede todo el tiempo en la programación del juego, por ejemplo, un vértice en un modelo 3D tendrá una posición, una coordenada de textura y un valor normal, generalmente también será inmutable. Un solo modelo puede tener un par de miles de vértices, o puede tener una docena, pero las estructuras proporcionan menos sobrecarga en general en este escenario de uso. He verificado esto a través de mi propio diseño de motor.
Chris D.


44
@ChrisW Ya veo, pero ¿esos valores no representan un rectángulo, es decir, un valor "único"? Al igual que Vector3D o Color, también tienen varios valores, pero ¿creo que representan valores únicos?
Marson Mao

Respuestas:


604

La fuente a la que hace referencia el OP tiene cierta credibilidad ... pero ¿qué pasa con Microsoft? ¿Cuál es la postura sobre el uso de la estructura? Busqué algo de aprendizaje adicional de Microsoft , y esto es lo que encontré:

Considere definir 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.

No defina una estructura a menos que el tipo tenga todas las características siguientes:

  1. Lógicamente representa un valor único, similar a los tipos primitivos (entero, doble, etc.).
  2. Tiene un tamaño de instancia menor de 16 bytes.
  3. Es inmutable.
  4. No tendrá que ser encuadrado con frecuencia.

Microsoft viola constantemente esas reglas

Bien, # 2 y # 3 de todos modos. Nuestro querido diccionario tiene 2 estructuras internas:

[StructLayout(LayoutKind.Sequential)]  // default for structs
private struct Entry  //<Tkey, TValue>
{
    //  View code at *Reference Source
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Enumerator : 
    IEnumerator<KeyValuePair<TKey, TValue>>, IDisposable, 
    IDictionaryEnumerator, IEnumerator
{
    //  View code at *Reference Source
}

* Fuente de referencia

La fuente 'JonnyCantCode.com' obtuvo 3 de 4, bastante perdonable ya que el # 4 probablemente no sería un problema. Si se encuentra boxeando una estructura, reconsidere su arquitectura.

Veamos por qué Microsoft usaría estas estructuras:

  1. Cada estructura Entryy Enumeratorrepresenta valores únicos.
  2. Velocidad
  3. Entrynunca se pasa como parámetro fuera de la clase Diccionario. La investigación adicional muestra que para satisfacer la implementación de IEnumerable, Dictionary utiliza la Enumeratorestructura que copia cada vez que se solicita un enumerador ... tiene sentido.
  4. Interno a la clase Diccionario. Enumeratores público porque Dictionary es enumerable y debe tener igual accesibilidad a la implementación de la interfaz IEnumerator, por ejemplo, IEnumerator getter.

Actualización : además, tenga en cuenta que cuando una estructura implementa una interfaz, como lo hace Enumerator, y se convierte a ese tipo implementado, la estructura se convierte en un tipo de referencia y se mueve al montón. Interna a la clase Diccionario, Enumerator sigue siendo un tipo de valor. Sin embargo, tan pronto como un método llama GetEnumerator(), IEnumeratorse devuelve un tipo de referencia .

Lo que no vemos aquí es ningún intento o prueba de requisito para mantener las estructuras inmutables o mantener un tamaño de instancia de solo 16 bytes o menos:

  1. No se declara nada en las estructuras anteriores readonly, no es inmutable
  2. El tamaño de estas estructuras podría superar los 16 bytes.
  3. Entrytiene una vida indeterminado (de Add(), a Remove(), Clear()o la recolección de basura);

Y ... 4. Ambas estructuras almacenan TKey y TValue, que todos sabemos que son bastante capaces de ser tipos de referencia (información adicional)

A pesar de las claves hash, los diccionarios son rápidos en parte porque instaurar una estructura es más rápido que un tipo de referencia. Aquí, tengo un Dictionary<int, int>que almacena 300,000 enteros aleatorios con claves incrementadas secuencialmente.

Capacidad: 312874 Tamaño de
memoria: 2660827 bytes
Tamaño completado : 5 ms Tiempo
total de llenado: 889 ms

Capacidad : número de elementos disponibles antes de que se deba cambiar el tamaño de la matriz interna.

MemSize : determinado serializando el diccionario en un MemoryStream y obteniendo una longitud de byte (lo suficientemente precisa para nuestros propósitos).

Redimensionado completado : el tiempo que lleva cambiar el tamaño de la matriz interna de 150862 elementos a 312874 elementos. Cuando te das cuenta de que cada elemento se copia secuencialmente Array.CopyTo(), eso no es nada malo.

Tiempo total para llenar : admitidamente sesgado debido al registro y un OnResizeevento que agregué a la fuente; Sin embargo, sigue siendo impresionante llenar 300k enteros mientras se redimensiona 15 veces durante la operación. Solo por curiosidad, ¿cuál sería el tiempo total para llenar si ya conociera la capacidad? 13ms

Entonces, ¿y si Entryfuera una clase? ¿Estos tiempos o métricas realmente diferirían tanto?

Capacidad: 312874 Tamaño de
memoria: 2660827 bytes
Tamaño completado: 26ms
Tiempo total para llenar: 964ms

Obviamente, la gran diferencia está en cambiar el tamaño. ¿Hay alguna diferencia si el Diccionario se inicializa con la Capacidad? No es suficiente para preocuparse por ... 12ms .

Lo que sucede es que, como Entryes una estructura, no requiere inicialización como un tipo de referencia. Esto es tanto la belleza como la ruina del tipo de valor. Para usar Entrycomo tipo de referencia, tuve que insertar el siguiente código:

/*
 *  Added to satisfy initialization of entry elements --
 *  this is where the extra time is spent resizing the Entry array
 * **/
for (int i = 0 ; i < prime ; i++)
{
    destinationArray[i] = new Entry( );
}
/*  *********************************************** */  

La razón por la que tuve que inicializar cada elemento de matriz Entrycomo tipo de referencia se puede encontrar en MSDN: Diseño de estructura . En breve:

No proporcione un constructor predeterminado para una estructura.

Si una estructura define un constructor predeterminado, cuando se crean matrices de la estructura, Common Language Runtime ejecuta automáticamente el constructor predeterminado en cada elemento de la matriz.

Algunos compiladores, como el compilador de C #, no permiten que las estructuras tengan constructores predeterminados.

En realidad es bastante simple y tomaremos prestado de las Tres leyes de la robótica de Asimov :

  1. La estructura debe ser segura de usar
  2. La estructura debe realizar su función de manera eficiente, a menos que esto viole la regla # 1
  3. La estructura debe permanecer intacta durante su uso a menos que se requiera su destrucción para cumplir con la regla # 1

... qué nos quitamos de esto : en resumen, ser responsables con el uso de los tipos de valor. Son rápidos y eficientes, pero tienen la capacidad de causar muchos comportamientos inesperados si no se mantienen adecuadamente (es decir, copias no intencionales).


8
En cuanto a las reglas de Microsoft, la regla sobre la inmutabilidad parece estar diseñada para desalentar el uso de los tipos de valor de tal manera que su comportamiento difiera del de los tipos de referencia, a pesar del hecho de que la semántica de valores mutables por partes puede ser útil . Si hacer que un tipo sea mutable por partes facilitaría el trabajo, y si las ubicaciones de almacenamiento del tipo deberían separarse lógicamente entre sí, el tipo debería ser una estructura "mutable".
supercat


2
El hecho de que muchos de los tipos de Microsoft violen esas reglas no representa un problema con esos tipos, sino que indica que las reglas no deberían aplicarse a todos los tipos de estructura. Si una estructura representa una sola entidad [como con Decimalo DateTime], entonces si no cumpliera con las otras tres reglas, debería ser reemplazada por una clase. Si una estructura contiene una colección fija de variables, cada una de las cuales puede contener cualquier valor que sea válido para su tipo [p Rectangle. Ej. ], Entonces debe cumplir con diferentes reglas, algunas de las cuales son contrarias a las de las estructuras de "valor único" .
supercat

44
@IAbstract: Algunas personas justificarían el Dictionarytipo de entrada sobre la base de que es solo un tipo interno, el rendimiento se consideró más importante que la semántica, o alguna otra excusa. Mi punto es que un tipo como Rectangledebería tener su contenido expuesto como campos editables individualmente no "porque" los beneficios de rendimiento superan las imperfecciones semánticas resultantes, sino porque el tipo representa semánticamente un conjunto fijo de valores independientes , por lo que la estructura mutable es a la vez más performante y semánticamente superior .
supercat

2
@supercat: Estoy de acuerdo ... y el punto central de mi respuesta fue que las 'pautas' son bastante débiles y las estructuras deben usarse con pleno conocimiento y comprensión de los comportamientos. Vea mi respuesta sobre la estructura mutable aquí: stackoverflow.com/questions/8108920/…
IAbstract

155

Cuando usted:

  1. no necesito polimorfismo
  2. querer semántica de valor, y
  3. desea evitar la asignación de almacenamiento dinámico y la sobrecarga de recolección de basura asociada.

Sin embargo, la advertencia es que las estructuras (arbitrariamente grandes) son más caras de repartir que las referencias de clase (generalmente una palabra de máquina), por lo que las clases podrían terminar siendo más rápidas en la práctica.


1
Esa es solo una "advertencia". También debe considerar "levantar" los tipos de valor y casos como (Guid)null(está bien emitir un valor nulo a un tipo de referencia), entre otras cosas.

1
más caro que en C / C ++? en C ++ la forma recomendada es pasar objetos por valor
Ion Todirel

@IonTodirel ¿No fue eso por razones de seguridad de la memoria, más que por el rendimiento? Siempre es una compensación, pero pasar 32 B por pila siempre (TM) será más lento que pasar una referencia 4 B por registro. Sin embargo , también tenga en cuenta que el uso de "valor / referencia" es un poco diferente en C # y C ++: cuando pasa una referencia a un objeto, sigue pasando por valor, aunque pase una referencia (usted ' re pasando el valor de la referencia, no una referencia a la referencia, básicamente). No es semántica de valor , pero técnicamente es "paso por valor".
Luaan

La copia de @Luaan es solo un aspecto de los costos. La indirección adicional debido al puntero / referencia también cuesta por acceso. En algunos casos, la estructura incluso se puede mover y, por lo tanto, ni siquiera es necesario copiarla.
Onur

@Onur eso es interesante. ¿Cómo se "mueve" sin copiar? Pensé que la instrucción asm "mov" en realidad no "mueve". Se copia.
Winger Sendon

148

No estoy de acuerdo con las reglas dadas en la publicación original. Aquí están mis reglas:

1) Utiliza estructuras para el rendimiento cuando se almacena en matrices. (ver también ¿ Cuándo son las estructuras la respuesta? )

2) Los necesita en el código que pasa datos estructurados a / desde C / C ++

3) No use estructuras a menos que las necesite:

  • Se comportan de manera diferente a los "objetos normales" ( tipos de referencia ) bajo asignación y al pasar como argumentos, lo que puede conducir a un comportamiento inesperado; Esto es particularmente peligroso si la persona que mira el código no sabe que está tratando con una estructura.
  • No pueden ser heredados.
  • Pasar estructuras como argumentos es más costoso que las clases.

44
+1 Sí, estoy totalmente de acuerdo con el n. ° 1 (esta es una gran ventaja cuando se trata de cosas como imágenes, etc.) y para señalar que son diferentes de los "objetos normales" y hay una forma conocida de saber esto, excepto por el conocimiento existente o examinando el tipo en sí. Además, no puede emitir un valor nulo a un tipo de estructura :-) En realidad, este es un caso en el que casi desearía que hubiera algún 'húngaro' para los tipos de valor no Core o una palabra clave 'struct' obligatoria en el sitio de declaración variable .

@pst: Es cierto que uno tiene que saber que algo es structsaber cómo se comportará, pero si algo tiene structcampos expuestos, eso es todo lo que uno debe saber. Si un objeto expone una propiedad de un tipo de estructura de campo expuesto, y si el código lee esa estructura a una variable y la modifica, se puede predecir con seguridad que dicha acción no afectará al objeto cuya propiedad se leyó a menos que se escriba la estructura espalda. Por el contrario, si la propiedad fuera un tipo de clase mutable, leerlo y modificarlo podría actualizar el objeto subyacente como se esperaba, pero ...
supercat

... también podría terminar cambiando nada, o podría cambiar o corromper objetos que uno no pretendía cambiar. Tener un código cuya semántica dice "cambia esta variable todo lo que quieras; los cambios no harán nada hasta que los guardes explícitamente en algún lugar" parece más claro que tener un código que diga "Estás obteniendo una referencia a algún objeto, que podría compartirse con cualquier número de otras referencias, o podría no compartirse en absoluto; tendrá que averiguar quién más podría tener referencias a este objeto para saber qué sucederá si lo cambia ".
supercat

Apunta con el # 1. Una lista llena de estructuras puede exprimir datos mucho más relevantes en cachés L1 / L2 que una lista llena de referencias de objetos (para la estructura de tamaño correcto).
Matt Stephenson

2
La herencia rara vez es la herramienta adecuada para el trabajo, y razonar demasiado sobre el rendimiento sin perfilar es una mala idea. En primer lugar, las estructuras se pueden pasar por referencia. En segundo lugar, pasar por referencia o por valor rara vez es un problema de rendimiento significativo. Por último, no está contabilizando la asignación de montón adicional y la recolección de basura que debe realizarse para una clase. Personalmente, prefiero pensar que las estructuras son datos y clases simples como cosas que hacen cosas (objetos), aunque también puede definir métodos en estructuras.
weberc2

88

Utilice una estructura cuando desee semántica de valor en lugar de semántica de referencia.

Editar

No estoy seguro de por qué la gente está rechazando esto, pero este es un punto válido, y se hizo antes de que el operador aclarara su pregunta, y es la razón básica más fundamental para una estructura.

Si necesita semántica de referencia, necesita una clase, no una estructura.


13
Todos saben eso. Parece que está buscando más que una respuesta "estructura es un tipo de valor".
TheSmurf

21
Es el caso más básico y debe indicarse para cualquiera que lea esta publicación y no lo sepa.
JoshBerke

3
No es que esta respuesta no sea cierta; obviamente lo es. Ese no es realmente el punto.
TheSmurf

55
@ Josh: Para cualquiera que no lo sepa, simplemente decir que es una respuesta insuficiente, ya que es muy probable que tampoco sepan lo que significa.
TheSmurf

1
Acabo de rechazar esto porque creo que una de las otras respuestas debería estar en la parte superior: cualquier respuesta que diga "Para interoperabilidad con código no administrado, de lo contrario, evítelo".
Daniel Earwicker

59

Además de la respuesta "es un valor", un escenario específico para usar estructuras es cuando sabes que tienes un conjunto de datos que está causando problemas de recolección de basura, y tienes muchos objetos. Por ejemplo, una gran lista / matriz de instancias de Persona. La metáfora natural aquí es una clase, pero si tiene una gran cantidad de instancias de Persona de larga duración, pueden terminar obstruyendo GEN-2 y causar paradas de GC. Si el escenario lo justifica, un enfoque potencial aquí es usar una matriz (no una lista) de estructuras de Persona , es decir Person[]. Ahora, en lugar de tener millones de objetos en GEN-2, tiene un solo fragmento en el LOH (supongo que no hay cadenas, etc., es decir, un valor puro sin referencias). Esto tiene muy poco impacto de GC.

Trabajar con estos datos es incómodo, ya que los datos probablemente estén sobredimensionados para una estructura, y no desea copiar valores gordos todo el tiempo. Sin embargo, acceder a él directamente en una matriz no copia la estructura, está en su lugar (en contraste con un indexador de lista, que copia). Esto significa mucho trabajo con índices:

int index = ...
int id = peopleArray[index].Id;

Tenga en cuenta que mantener los valores en sí mismos inmutables ayudará aquí. Para una lógica más compleja, use un método con un parámetro by-ref:

void Foo(ref Person person) {...}
...
Foo(ref peopleArray[index]);

Una vez más, esto está en su lugar: no hemos copiado el valor.

En escenarios muy específicos, esta táctica puede ser muy exitosa; sin embargo, es un scernario bastante avanzado que debe intentarse solo si sabe lo que está haciendo y por qué. El valor predeterminado aquí sería una clase.


+1 respuesta interesante. ¿Estaría dispuesto a compartir alguna anécdota del mundo real sobre el uso de este enfoque?
Jordão

@Jordao en el móvil, pero busca en google: + gravell + "asalto por GC"
Marc Gravell

1
Muchas gracias. Lo encontré aquí .
Jordão

2
@MarcGravell ¿Por qué mencionó: usar una matriz (no una lista) ? ListCreo, utiliza un Arraydetrás de escena. No ?
Royi Namir

44
@RoyiNamir También tenía curiosidad sobre esto, pero creo que la respuesta se encuentra en el segundo párrafo de la respuesta de Marc. "Sin embargo, acceder a él directamente en una matriz no copia la estructura, está en su lugar (en contraste con un indexador de lista, que copia)".
user1323245

40

De la especificación del lenguaje C # :

1.7 estructuras

Al igual que las clases, las estructuras son estructuras de datos que pueden contener miembros de datos y miembros de funciones, pero a diferencia de las clases, las estructuras son tipos de valor y no requieren asignación de montón. Una variable de un tipo de estructura almacena directamente los datos de la estructura, mientras que una variable de un tipo de clase almacena una referencia a un objeto asignado dinámicamente. Los tipos de estructura no admiten la herencia especificada por el usuario, y todos los tipos de estructura heredan implícitamente del objeto de tipo.

Las estructuras son particularmente útiles para pequeñas estructuras de datos que tienen una semántica de valor. Los números complejos, los puntos en un sistema de coordenadas o los pares clave-valor en un diccionario son buenos ejemplos de estructuras. El uso de estructuras en lugar de clases para estructuras de datos pequeñas puede hacer una gran diferencia en el número de asignaciones de memoria que realiza una aplicación. Por ejemplo, el siguiente programa crea e inicializa una matriz de 100 puntos. Con Point implementado como una clase, se crean instancias de 101 objetos separados, uno para la matriz y uno para los 100 elementos.

class Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

class Test
{
   static void Main() {
      Point[] points = new Point[100];
      for (int i = 0; i < 100; i++) points[i] = new Point(i, i);
   }
}

Una alternativa es hacer de Point una estructura.

struct Point
{
   public int x, y;

   public Point(int x, int y) {
      this.x = x;
      this.y = y;
   }
}

Ahora, solo se crea una instancia de un objeto, el de la matriz, y las instancias de Point se almacenan en línea en la matriz.

Los constructores de estructura se invocan con el nuevo operador, pero eso no implica que se esté asignando memoria. En lugar de asignar dinámicamente un objeto y devolverle una referencia, un constructor de estructura 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.

Con las clases, es posible que dos variables hagan referencia al mismo objeto y, por lo tanto, que las operaciones en una variable afecten al objeto al que hace referencia la otra variable. Con las estructuras, las variables tienen cada una su propia copia de los datos, y no es posible que las operaciones en una afecten a la otra. Por ejemplo, la salida producida por el siguiente fragmento de código depende de si Point es una clase o una estructura.

Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);

Si Point es una clase, la salida es 20 porque a y b hacen referencia al mismo objeto. Si Point es una estructura, el resultado es 10 porque la asignación de a a b crea una copia del valor, y esta copia no se ve afectada por la asignación posterior a ax

El ejemplo anterior destaca dos de las limitaciones de las estructuras. Primero, copiar una estructura completa suele ser menos eficiente que copiar una referencia de objeto, por lo que la transferencia de parámetros de asignación y valor puede ser más costosa con estructuras que con tipos de referencia. En segundo lugar, a excepción de los parámetros ref y out, no es posible crear referencias a estructuras, lo que descarta su uso en varias situaciones.


44
Si bien el hecho de que las referencias a las estructuras no pueden persistirse es a veces una limitación, también es una característica muy útil. Una de las principales debilidades de .net es que no hay una forma decente de pasar una referencia externa a un objeto mutable sin perder para siempre el control de ese objeto. Por el contrario, se puede dar de forma segura un método externo refa una estructura mutable y saber que cualquier mutación que realice el método externo se realizará antes de que regrese. Es una lástima .net no tiene ningún concepto de parámetros efímeros y valores de retorno de funciones, ya que ...
supercat

44
... eso permitiría reflograr la semántica ventajosa de estructuras pasadas con objetos de clase. Esencialmente, las variables locales, los parámetros y los valores de retorno de la función pueden ser persistentes (por defecto), retornables o efímeros. Se prohibiría que Code copiara cosas efímeras a cualquier cosa que sobreviviera al alcance actual. Las cosas retornables serían como cosas efímeras, excepto que podrían ser devueltas de una función. El valor de retorno de una función estaría sujeto a las restricciones más estrictas aplicables a cualquiera de sus parámetros "retornables".
supercat

34

Las estructuras son buenas para la representación atómica de datos, donde dichos datos pueden copiarse varias veces por el código. Clonar un objeto es en general más costoso que copiar una estructura, ya que implica asignar la memoria, ejecutar el constructor y desasignar / recolectar basura cuando se hace con él.


44
Sí, pero las estructuras grandes pueden ser más caras que las referencias de clase (al pasar a métodos).
Alex

27

Aquí hay una regla básica.

  • Si todos los campos miembros son tipos de valor, cree una estructura .

  • Si algún campo miembro es un tipo de referencia, cree una clase . Esto se debe a que el campo de tipo de referencia necesitará la asignación del montón de todos modos.

Exmaples

public struct MyPoint 
{
    public int X; // Value Type
    public int Y; // Value Type
}

public class MyPointWithName 
{
    public int X; // Value Type
    public int Y; // Value Type
    public string Name; // Reference Type
}

3
Los tipos de referencia inmutables como stringson semánticamente equivalentes a los valores, y almacenar una referencia a un objeto inmutable en un campo no implica una asignación de montón. La diferencia entre una estructura con campos públicos expuestos y un objeto de clase con campos públicos expuestos es que, dada la secuencia de código var q=p; p.X=4; q.X=5;, p.Xtendrá el valor 4 si aes un tipo de estructura y 5 si es un tipo de clase. Si desea poder modificar convenientemente los miembros del tipo, debe seleccionar 'clase' o 'estructura' en función de si desea que los cambios qafecten p.
supercat

Sí, estoy de acuerdo en que la variable de referencia estará en la pila, pero el objeto al que hace referencia existirá en el montón. Aunque las estructuras y las clases se comportan de manera diferente cuando se asignan a una variable diferente, no creo que sea un factor decisivo fuerte.
Usman Zafar

Las estructuras mutables y las clases mutables se comportan de manera completamente diferente; si uno tiene razón, el otro probablemente estará equivocado. No estoy seguro de cómo el comportamiento no sería un factor decisivo para determinar si usar una estructura o una clase.
supercat

Dije que no es un factor decisivo fuerte porque a menudo cuando estás creando una clase o estructura no estás seguro de cómo se usará. Entonces te concentras en cómo las cosas tienen más sentido desde la perspectiva del diseño. De todos modos, nunca he visto en un solo lugar en la biblioteca .NET donde una estructura contiene una variable de referencia.
Usman Zafar

1
El tipo de estructura ArraySegment<T>encapsula a T[], que siempre es un tipo de clase. El tipo de estructura a KeyValuePair<TKey,TValue>menudo se usa con tipos de clase como parámetros genéricos.
supercat

19

Primero: escenarios de interoperabilidad o cuando necesita especificar el diseño de la memoria

Segundo: cuando los datos son casi del mismo tamaño que un puntero de referencia de todos modos.


17

Debe usar una "estructura" en situaciones en las que desea especificar explícitamente el diseño de memoria utilizando StructLayoutAttribute , generalmente para PInvoke.

Editar: El comentario señala que puede usar class o struct con StructLayoutAttribute y eso es cierto. En la práctica, normalmente usaría una estructura: se asigna en la pila frente al montón, lo que tiene sentido si solo está pasando un argumento a una llamada a un método no administrado.


55
El StructLayoutAttribute se puede aplicar a estructuras o clases, por lo que esta no es una razón para usar estructuras.
Stephen Martin

¿Por qué tiene sentido si solo está pasando un argumento a una llamada a un método no administrado?
David Klempfner

16

Utilizo estructuras para empacar o desempacar cualquier tipo de formato de comunicación binario. Eso incluye leer o escribir en el disco, listas de vértices DirectX, protocolos de red o tratar datos cifrados / comprimidos.

Las tres pautas que enumeras no me han sido útiles en este contexto. Cuando necesito escribir cuatrocientos bytes de cosas en un orden particular, voy a definir una estructura de cuatrocientos bytes, y la llenaré con los valores no relacionados que se supone que tiene, y voy a configurarlo de la manera que tenga más sentido en ese momento. (De acuerdo, cuatrocientos bytes serían bastante extraños, pero cuando estaba escribiendo archivos de Excel para vivir, estaba lidiando con estructuras de hasta unos cuarenta bytes, porque así de grandes son algunos de los registros BIFF).


Sin embargo, ¿no podrías usar un tipo de referencia igual de fácil?
David Klempfner

15

Con la excepción de los valuetypes que son utilizados directamente por el tiempo de ejecución y varios otros para propósitos de PInvoke, solo debe usar valuetypes en 2 escenarios.

  1. Cuando necesite copiar semántica.
  2. Cuando necesita inicialización automática, normalmente en matrices de estos tipos.

# 2 parece ser parte de la razón de prevalencia estructura en clases de colección .Net ..
iAbstract

Si lo primero que se haría al crear una ubicación de almacenamiento de un tipo de clase sería crear una nueva instancia de ese tipo, almacenar una referencia en esa ubicación y nunca copiar la referencia en ningún otro lugar ni sobrescribirla, entonces una estructura y la clase se comportaría de manera idéntica. Las estructuras tienen una forma estándar conveniente de copiar todos los campos de una instancia a otra, y generalmente ofrecerán un mejor rendimiento en casos en los que uno nunca duplicaría una referencia a una clase (excepto el thisparámetro efímero utilizado para invocar sus métodos); Las clases permiten duplicar referencias.
supercat

13

.NET admite value typesy reference types(en Java, puede definir solo tipos de referencia). Las instancias reference typesse asignan en el montón administrado y se recolectan cuando no hay referencias pendientes a ellas. Las instancias de value types, por otro lado, se asignan en el stack, y por lo tanto, la memoria asignada se reclama tan pronto como finaliza su alcance. Y, por supuesto, value typespasar por valor y reference typespor referencia. Todos los tipos de datos primitivos de C #, excepto System.String, son tipos de valores.

Cuando usar struct sobre clase,

En C #, structsare value types, las clases son reference types. Puede crear tipos de valor, en C #, utilizando la enumpalabra clave y la structpalabra clave. El uso de a en value typelugar de a reference typedará como resultado menos objetos en el montón administrado, lo que resulta en una menor carga en el recolector de basura (GC), ciclos de GC menos frecuentes y, en consecuencia, un mejor rendimiento. Sin embargo, también value typestienen sus desventajas. Pasar por alto structes definitivamente más costoso que pasar una referencia, ese es un problema obvio. El otro problema es la sobrecarga asociada a boxing/unboxing. En caso de que se pregunte qué boxing/unboxingsignifica, siga estos enlaces para obtener una buena explicación sobreboxing yunboxing. Además del rendimiento, hay momentos en los que simplemente necesita tipos para tener una semántica de valor, que sería muy difícil (o feo) implementar si reference typeses todo lo que tiene. Debe usar value typessolamente, cuando necesite semántica de copia o necesite inicialización automática, normalmente en arraysestos tipos.


Copiar estructuras pequeñas o pasar por valor es tan barato como copiar o pasar una referencia de clase, o pasar las estructuras por ref. Pasar cualquier estructura de tamaño por refcostos es lo mismo que pasar una referencia de clase por valor. Copiar cualquier estructura de tamaño o pasar por valor es más barato que realizar una copia defensiva de un objeto de clase y almacenar o pasar una referencia a eso. Las clases de Big Times son mejores que las estructuras para almacenar valores son (1) cuando las clases son inmutables (para evitar copias defensivas), y cada instancia que se crea se pasará mucho, o ...
supercat

... (2) cuando por varias razones una estructura simplemente no sería utilizable [por ejemplo, porque uno necesita usar referencias anidadas para algo como un árbol, o porque uno necesita polimorfismo]. Tenga en cuenta que cuando se usan tipos de valor, generalmente se deben exponer los campos directamente ausentes por una razón particular para no hacerlo (mientras que con la mayoría de los tipos de clase los campos se deben incluir en las propiedades). Muchos de los llamados "males" de los tipos de valores mutables se derivan del ajuste innecesario de los campos en las propiedades (por ejemplo, mientras que algunos compiladores permitirían llamar a un establecedor de propiedades en una estructura de solo lectura porque a veces ...
supercat

... hacer lo correcto, todos los compiladores rechazarían adecuadamente los intentos de establecer campos directamente en tales estructuras; la mejor manera de garantizar que los compiladores rechacen readOnlyStruct.someMember = 5;no es hacer someMemberuna propiedad de solo lectura, sino convertirla en un campo.
supercat

12

Una estructura es un tipo de valor. Si asigna una estructura a una nueva variable, la nueva variable contendrá una copia del original.

public struct IntStruct {
    public int Value {get; set;}
}

La ejecución de los siguientes resultados en 5 instancias de la estructura almacenada en la memoria:

var struct1 = new IntStruct() { Value = 0 }; // original
var struct2 = struct1;  // A copy is made
var struct3 = struct2;  // A copy is made
var struct4 = struct3;  // A copy is made
var struct5 = struct4;  // A copy is made

// NOTE: A "copy" will occur when you pass a struct into a method parameter.
// To avoid the "copy", use the ref keyword.

// Although structs are designed to use less system resources
// than classes.  If used incorrectly, they could use significantly more.

Una clase es un tipo de referencia. Cuando asigna una clase a una nueva variable, la variable contiene una referencia al objeto de clase original.

public class IntClass {
    public int Value {get; set;}
}

La ejecución de los siguientes resultados en una sola instancia del objeto de clase en la memoria.

var class1 = new IntClass() { Value = 0 };
var class2 = class1;  // A reference is made to class1
var class3 = class2;  // A reference is made to class1
var class4 = class3;  // A reference is made to class1
var class5 = class4;  // A reference is made to class1  

Las estructuras pueden aumentar la probabilidad de un error de código. Si un objeto de valor se trata como un objeto de referencia mutable, un desarrollador puede sorprenderse cuando los cambios realizados se pierden inesperadamente.

var struct1 = new IntStruct() { Value = 0 };
var struct2 = struct1;
struct2.Value = 1;
// At this point, a developer may be surprised when 
// struct1.Value is 0 and not 1

12

Hice un pequeño punto de referencia con BenchmarkDotNet para comprender mejor el beneficio de "estructura" en números. Estoy probando el bucle a través de la matriz (o lista) de estructuras (o clases). La creación de esas matrices o listas está fuera del alcance del punto de referencia: está claro que la "clase" es más pesada utilizará más memoria e involucrará GC.

Entonces, la conclusión es: tenga cuidado con LINQ y las estructuras ocultas que encajonan / desempaquetan y que usan estructuras para microoptimizaciones permanecen estrictamente con las matrices.

PD Otro punto de referencia sobre el paso de struct / class a través de la pila de llamadas es https://stackoverflow.com/a/47864451/506147

BenchmarkDotNet=v0.10.8, OS=Windows 10 Redstone 2 (10.0.15063)
Processor=Intel Core i5-2500K CPU 3.30GHz (Sandy Bridge), ProcessorCount=4
Frequency=3233542 Hz, Resolution=309.2584 ns, Timer=TSC
  [Host] : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Clr    : Clr 4.0.30319.42000, 64bit RyuJIT-v4.7.2101.1
  Core   : .NET Core 4.6.25211.01, 64bit RyuJIT


          Method |  Job | Runtime |      Mean |     Error |    StdDev |       Min |       Max |    Median | Rank |  Gen 0 | Allocated |
---------------- |----- |-------- |----------:|----------:|----------:|----------:|----------:|----------:|-----:|-------:|----------:|
   TestListClass |  Clr |     Clr |  5.599 us | 0.0408 us | 0.0382 us |  5.561 us |  5.689 us |  5.583 us |    3 |      - |       0 B |
  TestArrayClass |  Clr |     Clr |  2.024 us | 0.0102 us | 0.0096 us |  2.011 us |  2.043 us |  2.022 us |    2 |      - |       0 B |
  TestListStruct |  Clr |     Clr |  8.427 us | 0.1983 us | 0.2204 us |  8.101 us |  9.007 us |  8.374 us |    5 |      - |       0 B |
 TestArrayStruct |  Clr |     Clr |  1.539 us | 0.0295 us | 0.0276 us |  1.502 us |  1.577 us |  1.537 us |    1 |      - |       0 B |
   TestLinqClass |  Clr |     Clr | 13.117 us | 0.1007 us | 0.0892 us | 13.007 us | 13.301 us | 13.089 us |    7 | 0.0153 |      80 B |
  TestLinqStruct |  Clr |     Clr | 28.676 us | 0.1837 us | 0.1534 us | 28.441 us | 28.957 us | 28.660 us |    9 |      - |      96 B |
   TestListClass | Core |    Core |  5.747 us | 0.1147 us | 0.1275 us |  5.567 us |  5.945 us |  5.756 us |    4 |      - |       0 B |
  TestArrayClass | Core |    Core |  2.023 us | 0.0299 us | 0.0279 us |  1.990 us |  2.069 us |  2.013 us |    2 |      - |       0 B |
  TestListStruct | Core |    Core |  8.753 us | 0.1659 us | 0.1910 us |  8.498 us |  9.110 us |  8.670 us |    6 |      - |       0 B |
 TestArrayStruct | Core |    Core |  1.552 us | 0.0307 us | 0.0377 us |  1.496 us |  1.618 us |  1.552 us |    1 |      - |       0 B |
   TestLinqClass | Core |    Core | 14.286 us | 0.2430 us | 0.2273 us | 13.956 us | 14.678 us | 14.313 us |    8 | 0.0153 |      72 B |
  TestLinqStruct | Core |    Core | 30.121 us | 0.5941 us | 0.5835 us | 28.928 us | 30.909 us | 30.153 us |   10 |      - |      88 B |

Código:

[RankColumn, MinColumn, MaxColumn, StdDevColumn, MedianColumn]
    [ClrJob, CoreJob]
    [HtmlExporter, MarkdownExporter]
    [MemoryDiagnoser]
    public class BenchmarkRef
    {
        public class C1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        public struct S1
        {
            public string Text1;
            public string Text2;
            public string Text3;
        }

        List<C1> testListClass = new List<C1>();
        List<S1> testListStruct = new List<S1>();
        C1[] testArrayClass;
        S1[] testArrayStruct;
        public BenchmarkRef()
        {
            for(int i=0;i<1000;i++)
            {
                testListClass.Add(new C1  { Text1= i.ToString(), Text2=null, Text3= i.ToString() });
                testListStruct.Add(new S1 { Text1 = i.ToString(), Text2 = null, Text3 = i.ToString() });
            }
            testArrayClass = testListClass.ToArray();
            testArrayStruct = testListStruct.ToArray();
        }

        [Benchmark]
        public int TestListClass()
        {
            var x = 0;
            foreach(var i in testListClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayClass()
        {
            var x = 0;
            foreach (var i in testArrayClass)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestListStruct()
        {
            var x = 0;
            foreach (var i in testListStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestArrayStruct()
        {
            var x = 0;
            foreach (var i in testArrayStruct)
            {
                x += i.Text1.Length + i.Text3.Length;
            }
            return x;
        }

        [Benchmark]
        public int TestLinqClass()
        {
            var x = testListClass.Select(i=> i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }

        [Benchmark]
        public int TestLinqStruct()
        {
            var x = testListStruct.Select(i => i.Text1.Length + i.Text3.Length).Sum();
            return x;
        }
    }

¿Has descubierto por qué las estructuras son mucho más lentas cuando se usan en listas y demás? ¿Es por el boxeo oculto y el unboxing que has mencionado? Si es así, ¿por qué sucede?
Marko Grdinic

El acceso a la estructura en la matriz debería ser más rápido solo porque no se requieren referencias adicionales. Boxing / Unboxing es el caso de linq.
Roman Pokrovskij

10

Los tipos de estructura en C # u otros lenguajes .net se usan generalmente para contener cosas que deberían comportarse como grupos de valores de tamaño fijo. Un aspecto útil de los tipos de estructura es que los campos de una instancia de tipo de estructura pueden modificarse modificando la ubicación de almacenamiento en la que se encuentra, y de ninguna otra manera. Es posible codificar una estructura de tal manera que la única forma de mutar cualquier campo sea construir una instancia completamente nueva y luego usar una asignación de estructura para mutar todos los campos del objetivo sobrescribiéndolos con valores de la nueva instancia, pero a menos que una estructura no proporcione medios para crear una instancia en la que sus campos tengan valores no predeterminados, todos sus campos serán mutables si la estructura misma se almacena en una ubicación mutable.

Tenga en cuenta que es posible diseñar un tipo de estructura para que esencialmente se comporte como un tipo de clase, si la estructura contiene un campo de tipo de clase privado y redirige a sus propios miembros al del objeto de clase envuelto. Por ejemplo, un PersonCollectionpodría ofrecer propiedades SortedByNamey SortedById, ambos tienen una referencia "inmutable" a un PersonCollection(establecido en su constructor) y se implementan GetEnumeratorllamando a creator.GetNameSortedEnumeratoro creator.GetIdSortedEnumerator. Tales estructuras se comportarían de manera muy similar a una referencia a a PersonCollection, excepto que sus GetEnumeratormétodos estarían vinculados a diferentes métodos en PersonCollection. También se podría tener una estructura que envuelva una parte de una matriz (por ejemplo, se podría definir una ArrayRange<T>estructura que contendría un T[]llamado Arr, un int Offsety un intLength, con una propiedad indexada a la que, para un índice idxen el rango de 0 a Length-1, accedería Arr[idx+Offset]). Desafortunadamente, si se footrata de una instancia de solo lectura de dicha estructura, las versiones actuales del compilador no permitirán operaciones como foo[3]+=4;porque no tienen forma de determinar si tales operaciones intentarían escribir en campos de foo.

También es posible diseñar una estructura que se comporte como un tipo de valor que contenga una colección de tamaño variable (que parecerá copiarse siempre que la estructura sea), pero la única forma de hacer que funcione es asegurarse de que no haya ningún objeto struct tiene una referencia que siempre estará expuesta a cualquier cosa que pueda mutarla. Por ejemplo, uno podría tener una estructura similar a una matriz que contiene una matriz privada, y cuyo método de "colocación" indexado crea una nueva matriz cuyo contenido es similar al del original, excepto por un elemento modificado. Desafortunadamente, puede ser algo difícil hacer que tales estructuras funcionen eficientemente. Si bien hay momentos en que la semántica de estructura puede ser conveniente (por ejemplo, poder pasar una colección tipo matriz a una rutina, tanto el llamador como el que llama saben que el código externo no modificará la colección,


10

No, no estoy totalmente de acuerdo con las reglas. Son buenas pautas a tener en cuenta con el rendimiento y la estandarización, pero no a la luz de las posibilidades.

Como puede ver en las respuestas, hay muchas formas creativas de usarlas. Por lo tanto, estas pautas deben ser solo eso, siempre en aras del rendimiento y la eficiencia.

En este caso, uso clases para representar objetos del mundo real en su forma más grande, uso estructuras para representar objetos más pequeños que tienen usos más exactos. La forma en que lo dijiste, "un todo más coherente". La palabra clave es coherente. Las clases serán más elementos orientados a objetos, mientras que las estructuras pueden tener algunas de esas características, aunque en menor escala. OMI

Los uso mucho en las etiquetas Treeview y Listview donde se puede acceder a los atributos estáticos comunes muy rápidamente. Siempre me ha costado obtener esta información de otra manera. Por ejemplo, en mis aplicaciones de base de datos, uso una vista de árbol donde tengo tablas, SP, funciones o cualquier otro objeto. Creo y relleno mi estructura, la pongo en la etiqueta, la extraigo, obtengo los datos de la selección, etc. ¡No haría esto con una clase!

Intento mantenerlos pequeños, usarlos en situaciones de instancia única y evitar que cambien. Es prudente tener en cuenta la memoria, la asignación y el rendimiento. Y las pruebas son muy necesarias.


Las estructuras pueden usarse de manera sensata para representar objetos inmutables livianos, o pueden usarse de manera sensata para representar conjuntos fijos de variables relacionadas pero independientes (por ejemplo, las coordenadas de un punto). El consejo en esa página es bueno para estructuras que están diseñadas para servir al propósito anterior, pero es incorrecto para estructuras que están diseñadas para servir al propósito posterior. Mi pensamiento actual es que las estructuras que tienen campos privados generalmente deben cumplir con la descripción indicada, pero muchas estructuras deben exponer todo su estado a través de campos públicos.
supercat

Si la especificación para un tipo de "punto 3d" indica que todo su estado está expuesto a través de miembros legibles x, y y z, y es posible crear una instancia con cualquier combinación de doublevalores para esas coordenadas, tal especificación lo obligaría a comportarse semánticamente de manera idéntica a una estructura de campo expuesto, excepto por algunos detalles del comportamiento de subprocesos múltiples (la clase inmutable sería mejor en algunos casos, mientras que la estructura de campo expuesto sería mejor en otros; una estructura llamada "inmutable" sería ser peor en todos los casos).
supercat

8

Mi regla es

1, siempre use clase;

2, si hay algún problema de rendimiento, trato de cambiar alguna clase para estructurar según las reglas que mencionó @IAbstract, y luego hago una prueba para ver si estos cambios pueden mejorar el rendimiento.


Un caso de uso sustancial que Microsoft ignora es cuando uno quiere que una variable de tipo Fooencapsule una colección fija de valores independientes (p. Ej., Coordenadas de un punto) que a veces querrá pasar como grupo y a veces quiere cambiar de forma independiente. No he encontrado ningún patrón para usar clases que combine ambos propósitos casi tan bien como una estructura simple de campo expuesto (que, al ser una colección fija de variables independientes, se ajusta perfectamente).
supercat

1
@supercat: Creo que no es del todo justo culpar a Microsoft por eso. El problema real aquí es que C # como lenguaje orientado a objetos simplemente no se enfoca en tipos de registros simples que solo exponen datos sin mucho comportamiento. C # no es un lenguaje de paradigmas múltiples en la misma medida que, por ejemplo, C ++. Dicho esto, también creo que muy pocas personas programan OOP puro, por lo que tal vez C # sea un lenguaje demasiado idealista. (Por mi parte, recientemente comencé a exponer public readonlycampos en mis tipos también, porque crear propiedades de solo lectura es simplemente demasiado trabajo para prácticamente ningún beneficio.)
stakx - ya no contribuye el

1
@stakx: no es necesario que se "enfoque" en tales tipos; reconocerlos por lo que son sería suficiente. La mayor debilidad con C # con respecto a las estructuras es su mayor problema en muchas otras áreas también: el lenguaje proporciona instalaciones inadecuadas para indicar cuándo ciertas transformaciones son o no apropiadas, y la falta de tales instalaciones genera decisiones de diseño desafortunadas. Por ejemplo, el 99% de las "estructuras mutables son malos" se debe a que el compilador de convertir MyListOfPoint[3].Offset(2,3);en var temp=MyListOfPoint[3]; temp.Offset(2,3);, una transformación que es falsa cuando se aplica ...
supercat

... al Offsetmétodo. La forma correcta de evitar ese código falso no debe ser hacer que las estructuras sean inmutables innecesariamente, sino permitir que métodos como Offsetser etiquetados con un atributo que prohíba la transformación mencionada anteriormente. Las conversiones numéricas implícitas también podrían haber sido mucho mejores si se pudieran etiquetar para que sean aplicables solo en los casos en que su invocación sería obvia. Si existen sobrecargas para foo(float,float)y foo(double,double), me postulo que tratar de utilizar una floaty una doublefrecuencia no se debería aplicar una conversión implícita, pero en su lugar debería haber un error.
supercat

Una asignación directa de un doublevalor a a float, o pasarlo a un método que puede tomar un floatargumento pero no double, casi siempre haría lo que el programador pretendía. Por el contrario, asignar floatexpresiones a doublesin un tipo de letra explícito es a menudo un error. El único momento en que permitir la double->floatconversión implícita causaría problemas sería cuando se seleccionara una sobrecarga menos que ideal. Yo diría que la forma correcta de prevenir eso no debería haber sido prohibir implícitamente double-> float, sino etiquetar las sobrecargas con atributos para no permitir la conversión.
supercat

8

Una clase es un tipo de referencia. Cuando se crea un objeto de la clase, la variable a la que se asigna el objeto contiene solo una referencia a esa memoria. Cuando la referencia del objeto se asigna a una nueva variable, la nueva variable se refiere al objeto original. Los cambios realizados a través de una variable se reflejan en la otra variable porque ambos se refieren a los mismos datos. Una estructura es un tipo de valor. Cuando se crea una estructura, la variable a la que se asigna la estructura contiene los datos reales de la estructura. Cuando la estructura se asigna a una nueva variable, se copia. La nueva variable y la variable original, por lo tanto, contienen dos copias separadas de los mismos datos. Los cambios realizados en una copia no afectan a la otra copia. En general, las clases se utilizan para modelar comportamientos más complejos o datos que se pretende modificar después de crear un objeto de clase.

Clases y estructuras (Guía de programación de C #)


Las estructuras también son muy buenas en los casos en que es necesario sujetar algunas variables relacionadas pero independientes junto con cinta adhesiva (por ejemplo, las coordenadas de un punto). Las pautas de MSDN son razonables si se intenta producir estructuras que se comporten como objetos, pero son mucho menos apropiadas al diseñar agregados; algunos de ellos están casi exactamente equivocados en la última situación. Por ejemplo, cuanto mayor es el grado de independencia de las variables encapsuladas por un tipo, mayor es la ventaja de usar una estructura de campo expuesto en lugar de una clase inmutable.
supercat

6

MITO # 1: LAS ESTRUCTAS SON CLASES LIGERAS

Este mito viene en una variedad de formas. Algunas personas creen que los tipos de valor no pueden o no deben tener métodos u otro comportamiento significativo; deben usarse como tipos simples de transferencia de datos, con solo campos públicos o propiedades simples. El tipo DateTime es un buen contraejemplo para esto: tiene sentido que sea un tipo de valor, en términos de ser una unidad fundamental como un número o un carácter, y también tiene sentido que pueda realizar cálculos basados ​​en es valioso. Mirando las cosas desde la otra dirección, los tipos de transferencia de datos a menudo deberían ser tipos de referencia de todos modos; la decisión debería basarse en el valor deseado o la semántica del tipo de referencia, no en la simplicidad del tipo. Otras personas creen que los tipos de valor son "más ligeros" que los tipos de referencia en términos de rendimiento. La verdad es que, en algunos casos, los tipos de valor son más efectivos: no requieren recolección de basura a menos que estén encuadrados, no tienen la sobrecarga de identificación de tipo y no requieren desreferenciación, por ejemplo. Pero de otras maneras, los tipos de referencia son más eficaces: el paso de parámetros, la asignación de valores a variables, la devolución de valores y operaciones similares solo requieren la copia de 4 u 8 bytes (dependiendo de si está ejecutando el CLR de 32 o 64 bits) ) en lugar de copiar todos los datos. ¡Imagínese si ArrayList fuera de alguna manera un tipo de valor "puro", y pasar una expresión ArrayList a un método implicara copiar todos sus datos! En casi todos los casos, el rendimiento no está realmente determinado por este tipo de decisión de todos modos. Los cuellos de botella casi nunca están donde cree que estarán, y antes de tomar una decisión de diseño basada en el rendimiento, Debes medir las diferentes opciones. Vale la pena señalar que la combinación de las dos creencias tampoco funciona. No importa cuántos métodos tenga un tipo (ya sea una clase o una estructura), la memoria tomada por instancia no se ve afectada. (Hay un costo en términos de la memoria ocupada para el código en sí, pero se incurre una vez en lugar de para cada instancia).

MITO # 2: TIPOS DE REFERENCIA EN VIVO EN EL HEAP; TIPOS DE VALOR EN VIVO EN LA PILA

Esto a menudo es causado por la pereza de la persona que lo repite. La primera parte es correcta: siempre se crea una instancia de un tipo de referencia en el montón. Es la segunda parte la que causa problemas. Como ya he señalado, el valor de una variable vive donde se declara, por lo que si tiene una clase con una variable de instancia de tipo int, el valor de esa variable para cualquier objeto dado siempre estará donde está el resto de los datos para el objeto: en el montón Solo las variables locales (variables declaradas dentro de los métodos) y los parámetros del método viven en la pila. En C # 2 y posteriores, incluso algunas variables locales no viven realmente en la pila, como verá cuando veamos métodos anónimos en el capítulo 5. ¿ESTOS ESTOS CONCEPTOS SON PERTINENTES AHORA? Es discutible que si está escribiendo código administrado, debe dejar que el tiempo de ejecución se preocupe por la mejor forma de usar la memoria. En efecto, la especificación del lenguaje no garantiza qué vive dónde; un futuro tiempo de ejecución puede crear algunos objetos en la pila si sabe que puede salirse con la suya, o el compilador de C # podría generar código que casi no usa la pila. El siguiente mito suele ser solo un problema de terminología.

MITO # 3: LOS OBJETOS SE PASAN POR REFERENCIA EN C # POR DEFECTO

Este es probablemente el mito más ampliamente propagado. Una vez más, las personas que hacen esta afirmación a menudo (aunque no siempre) saben cómo se comporta realmente C #, pero no saben qué significa realmente "pasar por referencia". Desafortunadamente, esto es confuso para las personas que saben lo que significa. La definición formal de pasar por referencia es relativamente complicada, involucra valores de l y terminología similar de informática, pero lo importante es que si pasa una variable por referencia, el método que está llamando puede cambiar el valor de la variable de la persona que llama cambiando su valor de parámetro. Ahora, recuerde que el valor de una variable de tipo de referencia es la referencia, no el objeto en sí. Puede cambiar el contenido del objeto al que se refiere un parámetro sin que el parámetro mismo se pase por referencia. Por ejemplo,

void AppendHello(StringBuilder builder)
{
    builder.Append("hello");
}

Cuando se llama a este método, el valor del parámetro (una referencia a un StringBuilder) se pasa por valor. Si tuviera que cambiar el valor de la variable del generador dentro del método, por ejemplo, con el enunciado generador = nulo; ese cambio no sería visto por la persona que llama, contrario al mito. Es interesante notar que no solo el bit "por referencia" del mito es inexacto, sino también el bit "los objetos se pasan". Los objetos mismos nunca se pasan, ya sea por referencia o por valor. Cuando se trata de un tipo de referencia, la variable se pasa por referencia o el valor del argumento (la referencia) se pasa por valor. Aparte de cualquier otra cosa, esto responde a la pregunta de qué sucede cuando nulo se usa como argumento de valor: si los objetos se pasaran, eso causaría problemas, ¡ya que no habría un objeto para pasar! En lugar, la referencia nula se pasa por valor de la misma manera que cualquier otra referencia sería. Si esta explicación rápida lo ha dejado desconcertado, puede consultar mi artículo, "Parámetros que pasan en C #" (http://mng.bz/otVt ), que entra en mucho más detalle. Estos mitos no son los únicos que existen. El boxeo y el desempaquetado vienen por su parte de malentendidos, que trataré de aclarar a continuación.

Referencia: C # in Depth 3rd Edition por Jon Skeet


1
Muy bien asumiendo que tienes razón. También muy bueno para agregar una referencia.
No,

5

Creo que una buena primera aproximación es "nunca".

Creo que una buena segunda aproximación es "nunca".

Si está desesperado por perf, considérelos, pero siempre mida.


24
No estaría de acuerdo con esa respuesta. Las estructuras tienen un uso legítimo en muchos escenarios. Aquí hay un ejemplo: ordenar los procesos cruzados de datos de manera atómica.
Franci Penov

25
Debería editar su publicación y elaborar sus puntos: ha dado su opinión, pero debe respaldarla con la razón por la que toma esta opinión.
Erik Forbes

44
Creo que necesitan un equivalente de la tarjeta Totin 'Chip ( en.wikipedia.org/wiki/Totin%27_Chip ) para usar structs. Seriamente.
Greg

44
¿Cómo una persona de 87.5K publica una respuesta como esta? ¿Lo hizo cuando era niño?
Rohit Vipin Mathews

3
@Rohit: fue hace seis años; los estándares del sitio eran muy diferentes entonces. Sin embargo, esta sigue siendo una mala respuesta, tienes razón.
Andrew Arnold

5

Estaba tratando con la Canalización con nombre de Windows Communication Foundation [WCF] y noté que tiene sentido usar Structs para garantizar que el intercambio de datos sea de tipo de valor en lugar de tipo de referencia .


1
Esta es la mejor pista de todas, en mi humilde opinión.
Ivan

4

La estructura C # es una alternativa ligera a una clase. Puede hacer casi lo mismo que una clase, pero es menos "costoso" usar una estructura en lugar de una clase. La razón de esto es un poco técnica, pero en resumen, las nuevas instancias de una clase se colocan en el montón, donde las estructuras recién instanciadas se colocan en la pila. Además, no se trata de referencias a estructuras, como las clases, sino que se trabaja directamente con la instancia de estructura. Esto también significa que cuando pasa una estructura a una función, es por valor, en lugar de como referencia. Hay más sobre esto en el capítulo sobre parámetros de función.

Por lo tanto, debe usar estructuras cuando desee representar estructuras de datos más simples, y especialmente si sabe que creará una gran cantidad de ellas. Hay muchos ejemplos en el marco .NET, donde Microsoft ha usado estructuras en lugar de clases, por ejemplo, la estructura Punto, Rectángulo y Color.



3

Los tipos de estructura o valor se pueden usar en los siguientes escenarios:

  1. Si desea evitar que el objeto sea recogido por la recolección de basura.
  2. Si es un tipo simple y ninguna función miembro modifica sus campos de instancia
  3. Si no es necesario derivar de otros tipos o derivar a otros tipos.

Puede obtener más información sobre los tipos de valores y los tipos de valores aquí en este enlace


3

Brevemente, use struct si:

1- sus propiedades / campos de objeto no necesitan ser cambiados. Quiero decir que solo quieres darles un valor inicial y luego leerlos.

2- las propiedades y los campos en su objeto son de tipo de valor y no son tan grandes.

Si ese es el caso, puede aprovechar las estructuras para un mejor rendimiento y una asignación de memoria optimizada, ya que solo usan pilas en lugar de pilas y montones (en clases)


2

Raramente uso una estructura para las cosas. Pero solo soy yo. Depende de si necesito que el objeto sea anulable o no.

Como se indicó en otras respuestas, uso clases para objetos del mundo real. También tengo la mentalidad de que las estructuras se utilizan para almacenar pequeñas cantidades de datos.


-11

Las estructuras son, en la mayoría de los casos, como clases / objetos. La estructura puede contener funciones, miembros y puede ser heredada. Pero las estructuras en C # se usan solo para la retención de datos . Las estructuras requieren menos RAM que las clases y son más fáciles de recolectar para el recolector de basura . Pero cuando usa funciones en su estructura, el compilador en realidad toma esa estructura de manera muy similar a la clase / objeto, por lo que si desea algo con funciones, use clase / objeto .


2
Las estructuras NO se pueden heredar, consulte msdn.microsoft.com/en-us/library/0taef578.aspx
HimBromBeere
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.