c# - ¿Puede un tipo inmutable cambiar su estado interno?
immutability (5)
Depende.
Si está documentando para los autores del código de cliente o el razonamiento como autor del código de cliente, entonces le preocupa la interfaz del componente (es decir, su estado y comportamiento observables externamente) y no sus detalles de implementación (como la representación interna). ).
En este sentido, un tipo es inmutable incluso si almacena en caché el estado, incluso si se inicializa perezosamente, etc., siempre que estas mutaciones no sean observables externamente. En otras palabras, un tipo es inmutable si se comporta como inmutable cuando se usa a través de su interfaz pública (o sus otros casos de uso previstos, si corresponde).
Por supuesto, esto puede ser difícil de conseguir (con un estado interno mutable, es posible que deba preocuparse por la seguridad de los subprocesos, la serialización / comportamiento de cálculo de referencias , etc.). Pero suponiendo que lo hagas bien (al menos en la medida en que lo necesites) no hay razón para no considerar ese tipo de cosas como algo inmutable.
Obviamente, desde el punto de vista de un compilador o un optimizador, tal tipo generalmente no se considera inmutable (a menos que el compilador sea lo suficientemente inteligente o tenga alguna "ayuda" como sugerencias o conocimientos previos de algunos tipos) y cualquier optimización que se haya previsto para tipos inmutables puede no ser aplicable, si este es el caso.
La pregunta es simple. ¿Un tipo que puede cambiar su estado interno sin que sea observable desde el exterior puede considerarse inmutable ?
Ejemplo simplificado:
public struct Matrix
{
bool determinantEvaluated;
double determinant;
public double Determinant
{
get //asume thread-safe correctness in implementation of the getter
{
if (!determinantEvaluated)
{
determinant = getDeterminant(this);
determinantEvaluated = true;
}
return determinant;
}
}
}
ACTUALIZACIÓN : Se aclaró el tema de la seguridad del hilo, ya que estaba causando distracción.
El propósito de especificar que un tipo sea inmutable es establecer el siguiente invariante:
- Si se observa que dos instancias de un tipo inmutable son iguales, cualquier referencia observable públicamente a una de ellas puede reemplazarse por una referencia a la otra sin afectar el comportamiento de ninguna de ellas.
Debido a que .NET ofrece la posibilidad de comparar dos referencias para la igualdad, no es posible lograr una equivalencia perfecta entre instancias inmutables. No obstante, el invariante anterior sigue siendo muy útil si uno considera que las verificaciones de igualdad de referencia están fuera del ámbito de las cosas de las que es responsable un objeto de clase.
Tenga en cuenta que bajo esta regla, una subclase puede definir campos más allá de los incluidos en una clase base inmutable, pero no debe exponerlos de tal manera que infrinja el invariante anterior. Además, una clase puede incluir campos mutables siempre que nunca cambien de ninguna manera que afecte el estado visible de una clase. Considere algo como el campo hash
en la clase de string
de Java. Si no es cero, el valor de hashCode
de la cadena es igual al valor almacenado en el campo. Si es cero, el valor de hashCode
de la cadena es el resultado de realizar ciertos cálculos en la secuencia de caracteres inmutable encapsulada por la cadena. Almacenar el resultado de los cálculos mencionados anteriormente en el campo hash
no afectará el código hash de la cadena; simplemente acelerará las solicitudes repetidas para el valor.
Sí, inmutable puede cambiar su estado, siempre que los cambios no se vean para otros componentes del software (generalmente cachés). Al igual que la física cuántica: un evento debe tener un observador para ser un evento.
En tu caso una posible implementación es algo así:
public class Matrix {
...
private Lazy<Double> m_Determinant = new Lazy<Double>(() => {
return ... //TODO: Put actual implementation here
});
public Double Determinant {
get {
return m_Determinant.Value;
}
}
}
Tenga en cuenta que Lazy<Double> m_Determinant
tiene un estado cambiante
m_Determinant.IsValueCreated
que es, sin embargo, inobservable .
Voy a citar al autor de Clojure Rich Hickey aquí :
Si un árbol cae en el bosque, ¿hace ruido?
Si una función pura muta algunos datos locales para generar un valor de retorno inmutable, ¿está bien?
Es perfectamente razonable mutar los objetos que están expuestos a las API que son inmutables al exterior por razones de rendimiento. Lo importante del objeto inmutable es su inmutabilidad hacia el exterior. Todo lo que está encapsulado dentro de ellos es un juego justo.
En cierto modo, en los lenguajes recolectados como C #, todos los objetos tienen algún estado debido al GC. Como consumidor que normalmente no debería preocuparte.
Voy a sacarme el cuello ...
No, un objeto inmutable no puede cambiar su estado interno en C # porque observar su memoria es una opción y, por lo tanto, puede observar el estado sin inicializar. Prueba:
public struct Matrix
{
private bool determinantEvaluated;
private double determinant;
public double Determinant
{
get
{
if (!determinantEvaluated)
{
determinant = 1.0;
determinantEvaluated = true;
}
return determinant;
}
}
}
entonces...
public class Example
{
public static void Main()
{
var unobserved = new Matrix();
var observed = new Matrix();
Console.WriteLine(observed.Determinant);
IntPtr unobservedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof (Matrix)));
IntPtr observedPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Matrix)));
byte[] unobservedMemory = new byte[Marshal.SizeOf(typeof (Matrix))];
byte[] observedMemory = new byte[Marshal.SizeOf(typeof (Matrix))];
Marshal.StructureToPtr(unobserved, unobservedPtr, false);
Marshal.StructureToPtr(observed, observedPtr, false);
Marshal.Copy(unobservedPtr, unobservedMemory, 0, Marshal.SizeOf(typeof (Matrix)));
Marshal.Copy(observedPtr, observedMemory, 0, Marshal.SizeOf(typeof (Matrix)));
Marshal.FreeHGlobal(unobservedPtr);
Marshal.FreeHGlobal(observedPtr);
for (int i = 0; i < unobservedMemory.Length; i++)
{
if (unobservedMemory[i] != observedMemory[i])
{
Console.WriteLine("Not the same");
return;
}
}
Console.WriteLine("The same");
}
}