variable - C#e inmutabilidad y campos de solo lectura... ¿una mentira?
sealed.net c# (5)
Creo que la respuesta de Eric va más allá del alcance de la pregunta original, hasta el punto de no responderla, así que voy a echarle una ojeada:
¿Qué es de solo lectura? Bueno, si estamos hablando de tipos de valores, es simple: una vez que el tipo de valor se inicializa y se le da un valor, nunca puede cambiar (al menos en lo que se refiere al compilador).
La confusión comienza a aparecer cuando hablamos sobre el uso de readonly con tipos de referencia. Es en este punto que necesitamos distinguir los dos componentes de un tipo de referencia:
- La "referencia" (variable, puntero) que apunta a la dirección en la memoria donde vive su objeto
- La memoria que contiene los datos a los que apunta la referencia
Una referencia a un objeto es en sí misma un tipo de valor. Cuando usa readonly con un tipo de referencia, hace que la referencia a su objeto sea inmutable, sin imponer la inmutabilidad en la memoria en la que vive el objeto.
Ahora, considere un objeto que contenga tipos de valores y referencias a otros objetos, y esos objetos contienen tipos de valores y referencias a otros objetos. Si tuviera que componer sus objetos de forma que todos los campos de todos los objetos sean de solo lectura, puede, en esencia, lograr la inmutabilidad que desea.
Descubrí que las personas afirman que el uso de todos los campos de solo lectura de una clase no necesariamente hace que la instancia de esa clase sea inmutable porque hay "formas" de cambiar los valores de campo de solo lectura incluso después de la inicialización (construcción).
¿Cómo? ¿De qué manera?
Entonces, mi pregunta es cuándo realmente podemos tener un objeto inmutable "real" en C #, que pueda usar con seguridad en el enhebrado.
¿También los tipos anónimos crean objetos inmutables? Y algunos dicen que LINQ usa objets inmutables internamente. ¿Cómo exactamente?
EDITAR: Me he concentrado en el código de trabajo "dentro" del sistema en lugar de usar el reflejo, etc. como Eric menciona.
Depende del tipo de campo. Si el campo en sí es de un tipo inmutable (por ejemplo, String
), está bien. Si es StringBuilder
, el objeto puede parecer que cambia aunque los campos no cambien sus valores, porque los objetos "incrustados" pueden cambiar.
Los tipos anónimos son exactamente lo mismo. Por ejemplo:
var foo = new { Bar = new StringBuilder() };
Console.WriteLine(foo); // { Bar = }
foo.Bar.Append("Hello");
Console.WriteLine(foo); // { Bar = Hello }
Entonces, básicamente, si tiene un tipo que desea ser inmutable correctamente, debe asegurarse de que solo se refiera a datos inmutables.
También está la cuestión de las estructuras que pueden tener campos de solo lectura pero aún así exponer métodos que se mutan al reasignar this
. Tales estructuras se comportan de forma un tanto extraña dependiendo de la situación exacta en que las llames. No es bueno, no lo hagas.
Eric Lippert ha escrito mucho sobre la inmutabilidad , es todo oro, como era de esperar ... ve a leer :) (No me había dado cuenta de que Eric estaba escribiendo una respuesta a esta pregunta cuando escribí este fragmento. Obviamente leí su respuesta también!)
Has hecho cinco preguntas allí. Voy a responder el primero:
Tener todos los campos de solo lectura en una clase no necesariamente hace que la instancia de esa clase sea inmutable porque hay "formas" de cambiar los valores de campo de solo lectura incluso después de la construcción. ¿Cómo?
¿Es posible cambiar un campo de solo lectura después de la construcción?
Sí, si eres lo suficientemente confiable como para romper las reglas de solo lectura .
¿Cómo funciona?
Cada bit de memoria de usuario en su proceso es mutable. Convenciones como campos de solo lectura pueden hacer que ciertos bits parezcan inmutables, pero si lo intentas lo suficiente, puedes mutarlos. Por ejemplo, puede tomar una instancia de objeto inmutable, obtener su dirección y cambiar los bits sin procesar directamente. Hacerlo puede requerir una gran cantidad de inteligencia y conocimiento de los detalles de implementación interna del administrador de memoria, pero de alguna manera el administrador de memoria logra mutar esa memoria, por lo que también puede hacerlo si lo intenta con la suficiente intensidad. También puede usar "reflexión privada" para romper varias partes del sistema de seguridad si es lo suficientemente confiable.
Por definición, se permite que el código completamente confiable rompa las reglas del sistema de seguridad . Eso es lo que significa "completamente confiable". Si su código de confianza elige usar herramientas como la reflexión privada o el código no seguro para romper las reglas de seguridad de la memoria, se permite que el código completamente confiable lo haga.
Por favor no. Hacerlo es peligroso y confuso. El sistema de seguridad de la memoria está diseñado para que sea más fácil razonar sobre la corrección de su código; violar deliberadamente sus reglas es una mala idea.
Entonces, ¿es "solo" una mentira? Bueno, supongo que te dije que si todos obedecen las reglas , todos reciben un trozo de tarta. ¿Es el pastel una mentira? Ese reclamo no es el reclamo "obtendrás un pedazo de pastel". Esa es la afirmación de que si todos obedecen las reglas , obtendrás un pedazo de pastel. Si alguien hace trampa y toma tu rebanada, no hay pastel para ti.
¿Es un campo de solo lectura de una clase de solo lectura? Sí, pero solo si todos obedecen las reglas . Entonces, los campos de solo lectura no son "una mentira". El contrato es, si todos obedecen las reglas del sistema, entonces se observa que el campo es de solo lectura. Si alguien rompe las reglas, entonces tal vez no sea así. Eso no hace la declaración "si todos obedecen las reglas, el campo es solo de lectura" ¡una mentira!
Una pregunta que no ha preguntado, pero quizás debería haber hecho, es si "de solo lectura" en los campos de una estructura también es una "mentira". Consulte ¿Funciona el uso de campos públicos de solo lectura para estructuras inmutables? para algunos pensamientos sobre esa pregunta. Los campos de solo lectura en una estructura son mucho más mentira que los campos de solo lectura en una clase.
En cuanto al resto de sus preguntas, creo que obtendrá mejores resultados si hace una pregunta por pregunta, en lugar de cinco preguntas por pregunta.
Readonly y Thread Safety son dos conceptos diferentes, como lo explica Eric Lippert .
Tal vez la "Gente" había escuchado tercera mano y se había comunicado mal sobre el privilegio especial que tiene el constructor con respecto a los campos de "solo lectura". Ellos no son de solo lectura
No solo eso, no son de asignación única. El constructor de una clase puede modificar el campo tantas veces como quiera. Todo sin romper ninguna "regla".
Los constructores también pueden llamar a otros métodos arbitrarios, pasándose a sí mismos como parámetros. Entonces, si usted es ese otro método, no puede estar seguro de si ese campo es inmutable aún, porque tal vez fue invocado por el constructor de ese objeto, y el constructor va a modificar el campo tan pronto como haya terminado.
Puedes probarlo tú mismo:
using System;
class App
{
class Foo
{
readonly int x;
public Foo() { x = 1; Frob(); x = 2; Frob(); }
void Frob() { Console.WriteLine(x); }
}
static void Main()
{
new Foo();
}
}
Este programa imprime 1, 2. ''x'' se modifica después de que Frob lo lea.
Ahora, dentro de cualquier cuerpo de método único, el valor debe ser constante: el constructor no puede delegar el acceso de modificación a otros métodos o Delegados, por lo que hasta que regrese un método, estoy seguro de que el campo deberá permanecer estable.
Todo lo anterior es sobre clases. Las estructuras son otra historia por completo.