¿Por qué las estructuras de C # son inmutables?

Solo tenía curiosidad por saber por qué las estructuras, las cuerdas, etc. son inmutables. Cuál es la razón para hacerlos inmutables y el rest de los objetos como mutable. ¿Cuáles son las cosas que se consideran para hacer que un objeto sea inmutable?

¿Hay alguna diferencia en la forma en que se asigna y desasigna la memoria para objetos mutables e inmutables?

Si este tema le interesa, tengo una serie de artículos sobre progtwigción inmutable en http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

Solo tenía curiosidad por saber por qué las estructuras, las cuerdas, etc. son inmutables.

Las estructuras y las clases no son inmutables por defecto, aunque es una mejor práctica hacer que las estructuras sean inmutables. También me gustan las clases inmutables.

Las cadenas son inmutables.

Cuál es la razón para hacerlos inmutables y el rest de los objetos como mutable.

Razones para hacer que todos los tipos sean inmutables:

  • Es más fácil razonar sobre objetos que no cambian. Si tengo una cola con tres elementos, sé que no está vacía ahora, no estaba vacía hace cinco minutos, no estará vacía en el futuro. ¡Es inmutable! Una vez que conozco un hecho al respecto, puedo usar ese hecho para siempre. Los hechos sobre objetos inmutables no se quedan obsoletos.

  • Un caso especial del primer punto: los objetos inmutables son mucho más fáciles de fabricar. La mayoría de los problemas de seguridad de hilos se deben a escrituras en un hilo y lecturas en otro; los objetos inmutables no tienen escrituras.

  • Los objetos inmutables se pueden desmontar y reutilizar. Por ejemplo, si tiene un árbol binario inmutable, puede usar sus subárboles izquierdo y derecho como subárboles de un árbol diferente sin preocuparse por ello. En una estructura mutable, normalmente termina haciendo copias de los datos para volver a usarlos porque no quiere que los cambios en un objeto lógico afecten a otro. Esto puede ahorrarle mucho tiempo y memoria.

Razones para hacer las estructuras inmutables

Hay muchas razones para hacer que las estructuras sean inmutables. Aquí hay solo uno.

Las estructuras se copian por valor, no por referencia. Es fácil tratar accidentalmente una estructura como copiada por referencia. Por ejemplo:

 void M() { S s = whatever; ... lots of code ... s.Mutate(); ... lots more code ... Console.WriteLine(s.Foo); ... } 

Ahora quiere refactorizar parte de ese código en un método de ayuda:

 void Helper(S s) { ... lots of code ... s.Mutate(); ... lots more code ... } 

¡INCORRECTO! Eso debería ser (ref S s) – si no haces eso, entonces la mutación ocurrirá en una copia de s. Si no permite las mutaciones en primer lugar, todos estos tipos de problemas desaparecen.

Razones para hacer cadenas inmutables

¿Recuerdas mi primer punto acerca de hechos sobre estructuras inmutables que permanecen en los hechos?

Supongamos que la cadena fuera mutable:

 public static File OpenFile(string filename) { if (!HasPermission(filename)) throw new SecurityException(); return InternalOpenFile(filename); } 

¿Qué pasa si la persona que llama hostil muta el nombre de archivo después de la comprobación de seguridad y antes de que se abra el archivo? ¡El código acaba de abrir un archivo al que podrían no tener permiso!

De nuevo, los datos mutables son difíciles de razonar. Desea que el hecho de que “esta persona que llama está autorizada a ver el archivo descrito por esta cadena” sea verdadero para siempre , no hasta que ocurra una mutación . Con cadenas mutables, para escribir código de seguridad tendríamos que estar constantemente haciendo copias de datos que sabemos que no cambian.

¿Cuáles son las cosas que se consideran para hacer que un objeto sea inmutable?

¿El tipo representa lógicamente algo que es un valor “eterno”? El número 12 es el número 12; no cambia Los enteros deben ser inmutables. El punto (10, 30) es el punto (10, 30); no cambia Los puntos deben ser inmutables. La cadena “abc” es la cadena “abc”; no cambia Las cadenas deben ser inmutables. La lista (10, 20, 30) no cambia. Y así.

A veces, el tipo representa cosas que sí cambian. El apellido de Mary Smith es Smith, pero mañana podría ser Mary Jones. O la señorita Smith hoy podría ser el doctor Smith mañana. El alienígena tiene cincuenta puntos de vida ahora pero tiene diez después de ser golpeado por el rayo láser. Algunas cosas se representan mejor como mutaciones.

¿Hay alguna diferencia en la forma en que se asigna y desasigna la memoria para objetos mutables e inmutables?

No como tal. Sin embargo, como mencioné antes, una de las cosas buenas de los valores inmutables es que puedes reutilizar partes de ellas sin hacer copias. Entonces, en ese sentido, la asignación de memoria puede ser muy diferente.

No construye … por eso las estructuras mutables son malvadas.

La creación de estructuras mutables puede generar todo tipo de comportamiento extraño en la aplicación y, por lo tanto, se considera una muy mala idea (debido al hecho de que se ven como un tipo de referencia pero en realidad son un tipo de valor y se copiarán cada vez que pase ellos alrededor).

Las cuerdas, por otro lado, lo son. Esto los hace intrínsecamente seguros para subprocesos así como también permite optimizaciones a través de la intercepción de cadenas. Si necesita construir una cadena complicada sobre la marcha, puede usar StringBuilder .

Los conceptos de mutabilidad e inmutabilidad tienen diferentes significados cuando se aplican a estructuras y clases. Un aspecto clave (a menudo, la debilidad clave) de las clases mutables es si Foo tiene un campo Bar de tipo List , que contiene una referencia a una lista que contiene (1,2,3), otro código que tiene una referencia a esa misma lista podría modificarlo, de modo que Bar mantenga una referencia a una lista que contenga (4,5,6), incluso si ese otro código no tiene acceso alguno a Bar . Por el contrario, si Foo tuviera un campo Biz de tipo System.Drawing.Point , la única forma en que cualquier cosa podría modificar cualquier aspecto de Biz sería tener acceso de escritura a ese campo .

Los campos (públicos y privados) de una estructura pueden ser mutados por cualquier código que pueda mutar la ubicación de almacenamiento en la que se almacena la estructura, y no puede ser mutado por ningún código que no pueda mutar la ubicación de almacenamiento donde se almacena. Si toda la información encapsulada dentro de una estructura se mantiene en sus campos, dicha estructura puede combinar efectivamente el control de un tipo inmutable con la conveniencia de un tipo mutable, a menos que la estructura esté codificada de tal manera que se elimine dicha conveniencia ( un hábito que, desafortunadamente, algunos progtwigdores de Microsoft recomiendan).

El “problema” con las estructuras es que cuando se invoca un método (incluida una implementación de propiedad) en una estructura en un contexto de solo lectura (o ubicación inmutable), el sistema copia la estructura, realiza el método en la copia temporal y silenciosamente descarta el resultado. Este comportamiento ha llevado a los progtwigdores a plantear la desafortunada noción de que la forma de evitar los problemas con los métodos de mutación es tener muchas estructuras que no permitan las actualizaciones por partes, cuando los problemas podrían haberse evitado simplemente reemplazando las propiedades con campos expuestos .

A propósito, algunas personas se quejan de que cuando una propiedad de clase devuelve una estructura convenientemente mutable, los cambios a la estructura no afectan a la clase de la que provienen. Postularía que es algo bueno: el hecho de que el elemento devuelto sea una estructura deja claro el comportamiento (especialmente si se trata de una estructura de campo expuesto). Compare un fragmento utilizando una estructura y propiedad hipotética en Drawing.Matrix con uno que use una propiedad real en esa clase tal como lo implementó Microsoft:

 // Estructura hipotética
 estructura pública {
   flotación pública xx, xy, yx, yy, dx, dy;
 } Transform2d;

 // Propiedad hipotética de "System.Drawing.Drawing2d.Matrix"
 public Transform2d Transform {get;}

 // Propiedad real de "System.Drawing.Drawing2d.Matrix"
 float público [] Elementos {get;  }

 // Código usando struct hipotética
 Transform2d myTransform = myMatrix.Transform;
 myTransform.dx + = 20;
 ... otro código usando myTransform

 // Código usando la propiedad real de Microsoft
 float [] myArray = myMatrix.Elements;
 myArray [4] + = 20;
 ... otro código usando myArray

En cuanto a la propiedad real de Microsoft, ¿hay alguna forma de saber si la escritura en myArray[4] afectará a myMatrix ? Incluso mirando la página http://msdn.microsoft.com/en-us/library/system.drawing.drawing2d.matrix.elements.aspx ¿hay alguna manera de saberlo? Si la propiedad se hubiera escrito utilizando el equivalente basado en la estructura, no habría confusión; la propiedad que devuelve la estructura no devolvería nada más ni menos que el valor presente de seis números. Cambiar myTransform.dx no sería nada más ni nada menos que una escritura en una variable de coma flotante que no estaba vinculada a ninguna otra cosa. A cualquier persona que no le guste el hecho de que cambiar myTransform.dx no afecta a myMatrix debe molestarse igualmente que escribir myArray[4] tampoco afecte myMatrix , excepto que la independencia de myMatrix y myTransform es evidente, mientras que la independencia de myMatrix myMatrix y myArray no lo son.

Un tipo de estructura no es inmutable. Sí, las cuerdas son. Hacer que tu propio tipo sea inmutable es fácil, simplemente no proporciones un constructor por defecto, hagas que todos los campos sean privados y no definas ningún método o propiedad que cambie el valor de un campo. Tener un método que debe mutar el objeto devolver un nuevo objeto en su lugar. Hay un ángulo de administración de la memoria, se tiende a crear muchas copias y basura.

Las estructuras pueden ser mutables, pero es una mala idea porque tienen semántica de copia. Si realiza un cambio en una estructura, en realidad podría estar modificando una copia. Hacer un seguimiento de exactamente lo que se ha cambiado es muy complicado.

Las estructuras mutables reproducen los errores.