¿Qué pasa con una estructura mutable que es inmutable desde el punto de vista del código externo?

StackOverflow https://stackoverflow.com/questions/3807030

Pregunta

Actualizar:Después de publicar esta pregunta, se me ocurrió que la principal desventaja de esta idea sería simplemente que ese tipo sería fácil de usar de manera incorrecta.Es decir, el tipo tendría que usarse de una manera muy específica para obtener algún beneficio.Lo que originalmente tenía en mente era algo que se usaría así (siguiendo con el SquareRootStruct ejemplo de la pregunta original solo por coherencia):

class SomeClass
{
    SquareRootStruct _key;

    public SomeClass(int value)
    {
        _key = new SquareRootStruct(value);
    }

    public double SquareRoot
    {
        // External code would have to access THIS property for caching
        // to provide any benefit.
        get { return _key.SquareRoot; }
    }

    public SquareRootStruct GetCopyOfKey()
    {
        // If _key has cached its calculation, then its copy will carry
        // the cached results with it.
        return _key;
    }
}

// elsewhere in the code...
var myObject = new SomeClass();

// If I do THIS, only a COPY of myObject's struct is caching a calculation,
// which buys me nothing.
double x = myObject.GetCopyOfKey().SquareRoot;

// So I would need to do this in order to get the benefit (which is,
// admittedly, confusing)...
double y = myObject.SquareRoot;

Entonces, considerando lo fácil que sería equivocarse, me inclino a pensar que tal vez Reed tenga razón (en su comentario) en que esto tendría más sentido como clase.


Supongamos que tengo un struct que quiero tener las siguientes caracteristicas:

  1. Inmutable desde un externo punto de vista
  2. Rápido para inicializar
  3. Cálculo diferido (y almacenamiento en caché) de ciertas propiedades

Obviamente la tercera característica implica mutabilidad, que todos sabemos que es malo (suponiendo que el mantra "¡No cree tipos de valores mutables!" se nos haya metido en la cabeza lo suficiente).Pero me parece que esto sería aceptable siempre y cuando la parte mutable sea visible solo internamente para el tipo mismo, y desde la perspectiva externa del código, el valor siempre sería el mismo.

Aquí hay un ejemplo de lo que estoy hablando:

struct SquareRootStruct : IEquatable<SquareRootStruct>
{
    readonly int m_value;  // This will never change.

    double m_sqrt;         // This will be calculated on the first property
                           // access, and thereafter never change (so it will
                           // appear immutable to external code).

    bool m_sqrtCalculated; // This flag will never be visible
                           // to external code.

    public SquareRootStruct(int value) : this()
    {
        m_value = value;
    }

    public int Value
    {
        get { return m_value; }
    }

    public double SquareRoot
    {
        if (!m_sqrtCalculated)
        {
            m_sqrt = Math.Sqrt((double)m_value);
            m_sqrtCalculated = true;
        }

        return m_sqrt;
    }

    public bool Equals(SquareRootStruct other)
    {
        return m_value == other.m_value;
    }

    public override bool Equals(object obj)
    {
        return obj is SquareRootStruct && Equals((SquareRootStruct)obj);
    }

    public override int GetHashCode()
    {
        return m_value;
    }
}

Ahora, obviamente esto es un ejemplo trivial, como Math.Sqrt Es casi seguro que no sea lo suficientemente costoso como para considerar que este enfoque valga la pena en este caso.Es sólo un ejemplo con fines ilustrativos.

Pero mi opinión es que esto logra mis tres objetivos donde los enfoques alternativos más obvios no lo harían.Específicamente:

  • I podría realizar el cálculo en el constructor del tipo;pero esto potencialmente no alcanzaría el segundo objetivo anterior (inicialización rápida).
  • I podría realizar el cálculo en cada acceso a la propiedad;pero esto potencialmente no alcanzaría el tercer objetivo anterior (almacenamiento en caché del resultado calculado para accesos futuros).

Entonces sí, esta idea conduciría efectivamente a un tipo de valor que es internamente mutable.Sin embargo, por lo que cualquier código externo podría decir (como yo lo veo), sería aparecer inmutable, al tiempo que trae consigo algunos beneficios de rendimiento (nuevamente, me doy cuenta de que el ejemplo anterior no ser un uso apropiado de esta idea;los "beneficios de rendimiento" de los que hablo dependerían de que el cálculo fuera lo suficientemente costoso como para justificar el almacenamiento en caché).

¿Me estoy perdiendo algo o es realmente una idea que vale la pena?

¿Fue útil?

Solución

No hay nada de malo en tener estructuras de datos inmutables que utilicen mutación internamente (siempre que se anuncie bien la seguridad del hilo).Es sólo que las estructuras se copian todo el tiempo, por lo que no se puede hacer mucho con la mutación.Esto no es un problema en C/C++ porque las estructuras generalmente se pasan por referencia, mientras que en C# es raro pasar estructuras por referencia.Dado que es difícil razonar sobre estructuras que se pasan por valor, se desaconsejan las estructuras mutables en C#.

Otros consejos

Lo que usted describe probablemente podría funcionar de alguna manera, pero con una advertencia importante:el resultado del cálculo lento puede almacenarse o no cuando se calcula.Por ejemplo, si el cálculo se realiza en las estructuras devueltas por una enumeración, los resultados se almacenarán en estructuras "temporales" y se descartarán en lugar de propagarse nuevamente a las almacenadas en la estructura de datos.

Esto se parece a un Futuro.Se mencionó un mejor soporte para Futures en las extensiones paralelas de C# 4.0.(Como calcularlos en otro núcleo/hilo en paralelo a su trabajo normal).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top