c# class struct immutability

c# - Clase inmutable vs estructura.



class immutability (4)

Dado que copiar la referencia es más barato que copiar la estructura, ¿por qué se usaría una estructura inmutable?

Esto no siempre es cierto. Copiar una referencia será de 8 bytes en un sistema operativo de 64 bits, que es potencialmente más grande que muchas estructuras.

También tenga en cuenta que la creación de la clase es probablemente más costosa. La creación de una estructura a menudo se realiza completamente en la pila (aunque hay muchas excepciones ), lo cual es muy rápido. Crear una clase requiere crear el identificador de objeto (para el recolector de basura), crear la referencia en la pila y realizar un seguimiento de la vida útil del objeto. Esto puede agregar presión GC, que también tiene un costo real.

Dicho esto, crear una estructura inmutable grande probablemente no sea una buena idea, lo cual es parte de por qué las Pautas para elegir entre Clases y Estructuras recomiendan usar siempre una clase si su estructura tendrá más de 16 bytes, si está recuadrada. Y otras cuestiones que hacen la diferencia más pequeña.

Dicho esto, a menudo baso mi decisión más en el uso previsto y el significado del tipo en cuestión. Los tipos de valor se deben usar para referirse a un solo valor (nuevamente, consulte las pautas) y, a menudo, tienen un significado semántico y un uso esperado diferente a las clases. A menudo, esto es tan importante como las características de rendimiento al elegir entre clase o estructura.

Las siguientes son las únicas formas en que las clases son diferentes de las estructuras en C # (corríjame si me equivoco):

  • Las variables de clase son referencias, mientras que las variables de estructura son valores, por lo tanto, el valor completo de estructura se copia en asignaciones y pasos de parámetros.
  • Las variables de clase son punteros almacenados en la pila que apuntan a la memoria en el montón, mientras que las variables de estructura se almacenan en el montón como valores

Supongamos que tengo una estructura inmutable, que es una estructura con campos que no se pueden modificar una vez que se inicializan. Cada vez que pase esta estructura como un parámetro o uso en asignaciones, el valor se copiaría y almacenaría en la pila.

Entonces supongamos que hago esta estructura inmutable para ser una clase inmutable. La instancia única de esta clase se crearía una vez, y solo la referencia a la clase se copiaría en asignaciones y pases de parámetros.

Si el objeto fuera mutable, el comportamiento en estos dos casos sería diferente: cuando uno cambiaría el objeto, en el primer caso se modificaría la copia de la estructura, mientras que en el segundo caso se cambiaría el objeto original. Sin embargo, en ambos casos el objeto es inmutable, por lo tanto, no hay diferencia si esto es realmente una clase o una estructura para el usuario de este objeto.

Dado que copiar la referencia es más barato que copiar la estructura, ¿por qué se usaría una estructura inmutable?

Además, dado que las estructuras mutables son malas , parece que no hay razón para usar estructuras en absoluto.

Donde me equivoco


De MSDN ;

Las clases son tipos de referencia y las estructuras son tipos de valor. Los tipos de referencia se asignan en el montón, y la gestión de la memoria es manejada por el recolector de basura. Los tipos de valor se asignan en la pila o en línea y se desasignan cuando están fuera del alcance. En general, los tipos de valor son más baratos de asignar y desasignar. Sin embargo, si se utilizan en escenarios que requieren una cantidad significativa de boxeo y unboxing, tienen un rendimiento deficiente en comparación con los tipos de referencia.

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

  • Lógicamente representa un solo valor, similar a los tipos primitivos (entero, doble, etc.).

  • Tiene un tamaño de instancia inferior a 16 bytes.

  • Es inmutable.

  • No tendrá que ser boxeado con frecuencia.

Por lo tanto, siempre debe usar una clase en lugar de una estructura, si su estructura tendrá más de 16 bytes. También lea en http://www.dotnetperls.com/struct


Hay dos casos de uso para estructuras. Las estructuras opacas son útiles para cosas que podrían implementarse usando clases inmutables, pero son lo suficientemente pequeñas como para que, incluso en las mejores circunstancias, no haya mucho beneficio, si es que alguno, de usar una clase, especialmente si la frecuencia con la que se crean y se descartan es una fracción significativa de la frecuencia con la que se copiarán simplemente. Por ejemplo, Decimal es una estructura de 16 bytes, por lo que mantener un millón de valores Decimal tomaría 16 megabytes. Si fuera una clase, cada referencia a una instancia Decimal tomaría 4 u 8 bytes, pero cada instancia distinta probablemente tomaría otros 20-32 bytes. Si uno tuviera muchos arreglos grandes cuyos elementos fueron copiados de un pequeño número de instancias de Decimal distintas, la clase podría ganar, pero en la mayoría de los escenarios, es más probable que tenga un arreglo con un millón de referencias a un millón de instancias de Decimal . lo que significaría que la estructura ganaría.

El uso de estructuras de esta manera generalmente solo es bueno si se aplican las pautas citadas de MSDN (aunque la guía de inmutabilidad es principalmente una consecuencia del hecho de que todavía no hay ninguna manera a través de la cual los métodos de la estructura puedan indicar que modifican la estructura subyacente). Si alguna de las tres últimas pautas no se aplica, es probable que una sea mejor si se usa una clase inmutable que una estructura. Sin embargo, si la primera directriz no se aplica, eso significa que uno no debe usar una estructura opaca , pero no debe usar una clase en su lugar.

En algunas situaciones, el propósito de un tipo de datos es simplemente unir un grupo de variables con cinta adhesiva para que sus valores puedan transmitirse como una unidad, pero aún permanezcan semánticamente como variables distintas. Por ejemplo, muchos métodos pueden necesitar pasar por grupos de tres números de punto flotante que representan coordenadas 3d. Si uno quiere dibujar un triángulo, es mucho más conveniente pasar tres parámetros de Point3d que nueve números de punto flotante. En muchos casos, el propósito de estos tipos no es impartir ningún comportamiento específico de dominio, sino simplemente proporcionar un medio para transmitir cosas de manera conveniente. En tales casos, las estructuras pueden ofrecer importantes ventajas de rendimiento en comparación con las clases, si se usan adecuadamente. Una estructura que se supone que representa tres variables del tipo de double sujeción con cinta adhesiva debe tener simplemente tres campos públicos de tipo double . Tal estructura permitirá que dos operaciones comunes se realicen de manera eficiente:

  1. Dada una instancia, tome una instantánea de su estado para que la instancia pueda modificarse sin molestar a la instantánea
  2. Dada una instancia que ya no es necesaria, de alguna manera aparece una instancia que es ligeramente diferente

Los tipos de clase inmutables permiten que el primero se realice a un costo fijo independientemente de la cantidad de datos que tenga la clase, pero son ineficientes en el segundo. Cuanto mayor sea la cantidad de datos que se supone que representa la variable, mayor será la ventaja de los tipos de clase inmutables en comparación con las estructuras al realizar la primera operación, y mayor será la ventaja de las estructuras de campo expuesto cuando se realiza la segunda.

Los tipos de clase mutables pueden ser eficientes en escenarios en los que la segunda operación domina, y la primera se necesita pocas veces, pero puede ser difícil para un objeto exponer los valores actuales en un objeto de clase mutable sin exponer el objeto a una modificación externa.

Tenga en cuenta que, dependiendo de los patrones de uso, las grandes estructuras de campos expuestos pueden ser mucho más eficientes que las estructuras opacas o los tipos de clase. La estructura de más de 17 bytes suele ser menos eficiente que las más pequeñas, pero aún así puede ser mucho más eficiente que las clases. Además, el costo de pasar una estructura como parámetro de ref no depende de su tamaño. Las estructuras grandes son ineficientes si se accede a ellas a través de propiedades en lugar de campos, se las pasa por valor innecesariamente, etc. Pero si se tiene cuidado de evitar operaciones redundantes de "copia", hay patrones de uso donde no hay un punto de equilibrio para las clases versus estructuras - estructuras simplemente funcionarán mejor.

Algunas personas pueden retroceder con horror ante la idea de que un tipo tenga campos expuestos, pero sugeriría que una estructura como la que describo no debería considerarse tanto como una entidad en sí misma, sino más bien una extensión de las cosas que se leen. o escríbelo. Por ejemplo:

public struct SlopeAndIntercept { public double Slope,Intercept; } public SlopeAndIntercept FindLeastSquaresFit() ...

El código que va a realizar un ajuste por mínimos cuadrados de un grupo de puntos tendrá que hacer una cantidad significativa de trabajo para encontrar la pendiente o la intersección en Y de la línea resultante; Encontrar ambos no costaría mucho más. El código que llama al método FindLeastSquaresFit probablemente querrá tener la pendiente en una variable y la intersección en otra. Si tal código hace:

var resultLine = FindLeastSquaresFit();

El resultado será crear efectivamente dos variables resultLine.Slope y resultLine.Intercept que el método puede manipular como crea conveniente. Los campos de resultLine no pertenecen realmente a SlopeIntercept , ni a FindLeastSquaresFit ; Pertenecen al código que declara resultLine . La situación es un poco diferente de si el método se utilizara como:

double Slope, Intercept; FindLeastSquaresFit(out Slope, out Intercept);

En ese contexto, sería claro que inmediatamente después de la llamada a la función, las dos variables tienen el significado asignado por el método, pero que su significado en cualquier otro momento dependerá de qué otra cosa haga el método con ellas. Igualmente para los campos de la citada estructura.

Hay algunas situaciones en las que puede ser mejor devolver datos utilizando una clase inmutable en lugar de una estructura transparente. Entre otras cosas, usar una clase facilitará las futuras versiones de una función que devuelve un Foo para devolver algo que incluye información adicional. Por otro lado, hay muchas situaciones en las que el código esperará tratar con un conjunto específico de cosas discretas, y cambiar ese conjunto de cosas cambiaría fundamentalmente lo que los clientes tienen que ver con eso. Por ejemplo, si uno tiene un montón de código que trata con los puntos (x, y), agregar una coordenada "z" requerirá que ese código se vuelva a escribir, y no hay nada que el tipo "punto" pueda hacer para mitigar eso.


La respuesta de Reed es bastante buena, pero solo para agregar algunos puntos adicionales:

Por favor corrígeme si estoy equivocado

Básicamente estás en el camino correcto aquí. Has cometido el error común de confundir variables con valores . Las variables son lugares de almacenamiento; Los valores se almacenan en variables. Y estás coqueteando con el mito común de que "los tipos de valor van en la pila"; más bien, las variables van en el almacenamiento a corto o largo plazo , porque las variables son ubicaciones de almacenamiento . Si una variable se almacena a corto o largo plazo depende de su vida útil conocida , no de su tipo .

Pero todo eso no es particularmente relevante para su pregunta, que se reduce a pedir una refutación de este silogismo:

  • Las estructuras mutables son malvadas.
  • La copia de referencia es más barata que la copia de estructura, por lo que las estructuras inmutables son siempre peores.
  • Por lo tanto, nunca hay ningún uso para estructuras.

Podemos refutar el silogismo de varias maneras.

Primero, sí, las estructuras mutables son malvadas. Sin embargo, a veces son muy útiles porque en algunos escenarios limitados, puede obtener una ventaja de rendimiento. No recomiendo este enfoque a menos que se hayan agotado otras vías razonables y haya un problema real de rendimiento.

En segundo lugar, la copia de referencia no es necesariamente más barata que la copia de estructura. Las referencias generalmente se implementan como punteros gestionados de 4 u 8 bytes (aunque eso es un detalle de la implementación; podrían implementarse como controladores opacos). Copiar una estructura de tamaño de referencia no es ni más barato ni más costoso que copiar una referencia de tamaño de referencia.

En tercer lugar, incluso si la copia de referencia es más barata que la copia de estructura, las referencias se deben anular para acceder a sus campos. Desreferenciación no es costo cero! ¡No solo hace falta que los ciclos de la máquina eliminen una referencia, sino que puede estropear la memoria caché del procesador y hacer que las futuras referencias sean mucho más caras!

Cuarto, incluso si la copia de referencia es más barata que la copia struct, ¿a quién le importa? Si ese no es el cuello de botella que está produciendo un costo de rendimiento inaceptable, entonces cuál es más rápido es completamente irrelevante.

En quinto lugar, las referencias son mucho más caras en el espacio de memoria que las estructuras.

Sexto, las referencias agregan gastos porque la red de referencias debe ser rastreada periódicamente por el recolector de basura; El recolector de basura puede ignorar por completo las estructuras "blittable". La recolección de basura es un gran gasto.

Séptimo, los tipos de valores inmutables no pueden ser nulos, a diferencia de los tipos de referencia. Usted sabe que cada valor es un buen valor. Y como Reed señaló, para obtener un buen valor de un tipo de referencia, debe ejecutar un asignador y un constructor . Eso no es barato.

En octavo lugar, los tipos de valores representan valores y los programas a menudo tratan sobre la manipulación de valores. Tiene sentido "hornear" las metáforas de "valor" y "referencia" en un idioma, sin importar cuál sea "más barato".