valores son que objetos mutables los inmutables inmutabilidad ejemplos dios derecho clases c# oop domain-driven-design

c# - son - Elegir entre objetos inmutables y estructuras para objetos de valor



objetos mutables e inmutables java (11)

¿Cómo se elige entre implementar un objeto de valor (el ejemplo canónico es una dirección) como un objeto inmutable o una estructura?

¿Hay beneficios de rendimiento, semánticos o de otro tipo al elegir uno sobre el otro?


¿Cuál es el costo de copiar instancias si se pasa por valor?

Si es alto, entonces el tipo de referencia (clase) inmutable, de lo contrario, valorizará (struct) el tipo.


Como regla general, un tamaño de estructura no debe exceder los 16 bytes; de lo contrario, pasarlo entre los métodos puede ser más costoso que las referencias de objetos que pasan, que son solo 4 bytes (en una máquina de 32 bits) de largo.

Otra preocupación es un constructor predeterminado. Una estructura siempre tiene un constructor predeterminado (sin parámetros y público); de lo contrario, las declaraciones como

T[] array = new T[10]; // array with 10 values

no funcionaría.

Además, es cortés para las estructuras para sobrescribir los operadores == y the != Y para implementar la IEquatable<T> .


En el mundo de hoy (estoy pensando C # 3.5) no veo la necesidad de estructuras (EDITAR: además de en algunos casos de nicho).

Los argumentos pro-struct parecen estar basados ​​principalmente en los beneficios de rendimiento percibidos. Me gustaría ver algunos puntos de referencia (que replican un escenario del mundo real) que ilustran esto.

La noción de usar una estructura para estructuras de datos "livianas" parece demasiado subjetiva para mi gusto. ¿Cuándo dejan de ser livianos los datos? Además, al agregar funcionalidad al código que usa una estructura, ¿cuándo decidirías cambiar ese tipo a una clase?

Personalmente, no recuerdo la última vez que utilicé una estructura en C #.

Editar

Sugiero que el uso de una estructura en C # por razones de rendimiento es un caso claro de Optimización prematura *

* a menos que la aplicación haya tenido un perfil de desempeño y el uso de una clase haya sido identificado como un cuello de botella de rendimiento

Editar 2

Estados de MSDN :

El tipo de estructura es adecuado para representar objetos livianos como Punto, Rectángulo y Color. Aunque es posible representar un punto como una clase, una estructura es más eficiente en algunos escenarios. Por ejemplo, si declara una matriz de objetos de 1000 puntos, asignará memoria adicional para hacer referencia a cada objeto. En este caso, la estructura es menos costosa.

A menos que necesite semántica de tipo de referencia, el sistema puede manejar una clase de menor tamaño que 16 bytes de forma más eficiente como una estructura.


En general, no recomendaría structs para objetos comerciales. Si bien PUEDE obtener una pequeña cantidad de rendimiento al dirigirse en esta dirección, ya que se está ejecutando en la pila, terminará por limitarse de alguna manera y el constructor predeterminado puede ser un problema en algunos casos.

Yo diría que esto es aún más imperativo cuando tienes un software que se lanza al público.

Las estructuras son buenas para tipos simples, por lo que se ve a Microsoft usando estructuras para la mayoría de los tipos de datos. De la misma manera, las estructuras están bien para los objetos que tienen sentido en la pila. La estructura de punto, mencionada en una de las respuestas, es un buen ejemplo.

¿Cómo decido? Por lo general, prefiero objetar y si parece ser algo que se beneficiaría de ser una estructura, que como regla sería un objeto bastante simple que solo contiene tipos simples implementados como estructuras, entonces lo analizaré detenidamente y determinaré si se trata de una estructura. sentido.

Usted menciona una dirección como su ejemplo. Examinemos uno, como una clase.

public class Address { public string AddressLine1 { get; set; } public string AddressLine2 { get; set; } public string City { get; set; } public string State { get; set; } public string PostalCode { get; set; } }

Piensa en este objeto como una estructura. En la consideración, considere los tipos incluidos dentro de esta dirección "struct", si lo codificó de esa manera. ¿Ves algo que podría no funcionar como quieres? Considere el posible beneficio de rendimiento (es decir, ¿hay uno)?


Desde una perspectiva de modelado de objetos, aprecio las estructuras porque me permiten usar el compilador para declarar ciertos parámetros y campos como no anulables. Por supuesto, sin una semántica de constructor especial (como en Spec # ), esto solo se aplica a los tipos que tienen un valor ''cero'' natural. (De ahí la respuesta de Bryan Watt ''aunque experimento''.)


Factores: construcción, requisitos de memoria, boxeo.

Normalmente, las restricciones del constructor para las estructuras, sin constructores explícitos sin parámetros, sin construcción base , decide si una estructura debe ser utilizada en absoluto. Por ejemplo, si el constructor sin parámetros no debe inicializar los miembros a valores predeterminados, utilice un objeto inmutable.

Si aún puede elegir entre los dos, decida sobre los requisitos de memoria. Los artículos pequeños deben almacenarse en estructuras, especialmente si espera muchas instancias.

Ese beneficio se pierde cuando las instancias se empaquetan (por ejemplo, se captura para una función anónima o se almacena en un contenedor no genérico); incluso se comienza a pagar un extra por el boxeo.

¿Qué es "pequeño", qué es "muchos"?

La sobrecarga para un objeto es (IIRC) 8 bytes en un sistema de 32 bits. Tenga en cuenta que con unos pocos cientos de instancias, esto ya puede decidir si un bucle interno se ejecuta completamente en caché o si invoca GC. Si espera decenas de miles de instancias, esta puede ser la diferencia entre ejecutar y rastrear.

Desde ese POV, el uso de estructuras NO es una optimización prematura.

Entonces, como reglas generales :

Si la mayoría de las instancias se encasillarán, use objetos inmutables.
De lo contrario, para objetos pequeños, use un objeto inmutable solo si la construcción de la estructura daría lugar a una interfaz incómoda y usted no esperaría más de miles de instancias.


Hay algunas cosas a considerar:

Una estructura se asigna en la pila (generalmente). Es un tipo de valor, por lo que pasar los datos a través de los métodos puede ser costoso si es demasiado grande.

Una clase se asigna en el montón. Es un tipo de referencia, por lo que pasar el objeto a través de métodos no es tan costoso.

En general, utilizo estructuras para objetos inmutables que no son muy grandes. Solo los uso cuando hay una cantidad limitada de datos en ellos o deseo inmutabilidad. Un ejemplo es la estructura DateTime . Me gusta pensar que si mi objeto no es tan liviano como algo como DateTime , probablemente no valga la pena usarlo como estructura. Además, si mi objeto no tiene sentido que se pase como un tipo de valor (también como DateTime ), puede que no sea útil usarlo como una estructura. La inmutabilidad es clave aquí sin embargo. Además, quiero enfatizar que las estructuras no son inmutables por defecto. Tienes que hacerlos inmutables por diseño.

En el 99% de las situaciones que encuentro, una clase es lo apropiado para usar. Me encuentro que no necesito clases inmutables muy a menudo. Es más natural para mí pensar que las clases son mutables en la mayoría de los casos.


¿Cómo se elige entre implementar un objeto de valor (el ejemplo canónico es una dirección) como un objeto inmutable o una estructura?

Creo que tus opciones son incorrectas El objeto y la estructura inmutables no son opuestos, ni son las únicas opciones. Por el contrario, tienes cuatro opciones:

  • Clase
    • mudable
    • inmutable
  • Struct
    • mudable
    • inmutable

Yo argumento que en .NET, la elección por defecto debe ser una clase mutable para representar la lógica y una clase inmutable para representar una entidad . En realidad, tiendo a elegir clases inmutables incluso para implementaciones lógicas, si es posible. Las estructuras deben reservarse para los tipos pequeños que emulan la semántica de valores, por ejemplo, un tipo de Date personalizado, un número Complex tipo entidades similares. El énfasis aquí es pequeño, ya que no desea copiar grandes cantidades de datos, y la indirección a través de las referencias es realmente barata (por lo que no ganamos mucho utilizando estructuras). Tiendo a hacer estructuras siempre inmutables (no puedo pensar en una sola excepción en este momento). Como esto se ajusta mejor a la semántica de los tipos de valores intrínsecos, creo que es una buena regla a seguir.


Las estructuras son estrictamente para usuarios avanzados (junto con out y ref).

Sí, las estructuras pueden dar un gran rendimiento cuando se utiliza la referencia, pero hay que ver qué memoria están usando. Quién controla la memoria, etc.

Si no usas ref y outs con structs, no valen la pena, si esperas errores desagradables :-)


De hecho, no recomiendo usar estructuras .NET para la implementación de Value Objects. Hay dos razones:

  • Las estructuras no admiten la herencia
  • Los ORM no manejan el mapeo para estructurar bien

Aquí describo este tema en detalle: objetos de valor explicados


Me gusta usar un experimento mental:

¿Tiene sentido este objeto cuando solo se llama un constructor vacío?

Editar a petición de Richard E.

Un buen uso de struct es envolver las primitivas y llevarlas a rangos válidos.

Por ejemplo, la probabilidad tiene un rango válido de 0-1. Usar un decimal para representar esto en todas partes es propenso a errores y requiere validación en cada punto de uso.

En cambio, puede envolver una primitiva con validación y otras operaciones útiles. Esto pasa el experimento mental porque la mayoría de los primitivos tienen un estado 0 natural.

Aquí hay un ejemplo de uso de struct para representar la probabilidad:

public struct Probability : IEquatable<Probability>, IComparable<Probability> { public static bool operator ==(Probability x, Probability y) { return x.Equals(y); } public static bool operator !=(Probability x, Probability y) { return !(x == y); } public static bool operator >(Probability x, Probability y) { return x.CompareTo(y) > 0; } public static bool operator <(Probability x, Probability y) { return x.CompareTo(y) < 0; } public static Probability operator +(Probability x, Probability y) { return new Probability(x._value + y._value); } public static Probability operator -(Probability x, Probability y) { return new Probability(x._value - y._value); } private decimal _value; public Probability(decimal value) : this() { if(value < 0 || value > 1) { throw new ArgumentOutOfRangeException("value"); } _value = value; } public override bool Equals(object obj) { return obj is Probability && Equals((Probability) obj); } public override int GetHashCode() { return _value.GetHashCode(); } public override string ToString() { return (_value * 100).ToString() + "%"; } public bool Equals(Probability other) { return other._value.Equals(_value); } public int CompareTo(Probability other) { return _value.CompareTo(other._value); } public decimal ToDouble() { return _value; } public decimal WeightOutcome(double outcome) { return _value * outcome; } }