2011-05-19 13 views
50

¿Es esta una forma adecuada de declarar estructuras inmutables?¿Funciona el uso de campos públicos de solo lectura para estructuras inmutables?

public struct Pair 
{ 
    public readonly int x; 
    public readonly int y; 

    // Constructor and stuff 
} 

No puedo pensar por qué esto podría tener problemas, pero solo quería preguntar para asegurarme.

En este ejemplo, utilicé ints. ¿Qué sucede si utilizo una clase en su lugar, pero esa clase también es inmutable, como tal? Eso debería funcionar bien también, ¿no?

public struct Pair 
{ 
    public readonly (immutableClass) x; 
    public readonly (immutableClass) y; 

    // Constructor and stuff 
} 

(Aparte: Entiendo que el uso de propiedades es más generalizables y permite cambiar, pero esta estructura se pretende, literalmente, a solo almacenar dos valores Estoy interesado en la cuestión inmutabilidad aquí..)

+0

'propiedades readonly'/miembros sólo se pueden establecer desde dentro del constructor (a más tardar). No se pueden establecer con la inicialización de la propiedad sintaxis. –

+0

Es posible que desee comprobar [Tipos inmutables: comprender sus beneficios y usarlos] (http://codebetter.com/patricksmacchia/2008/01/13/immutable-types-understand-them-and-use-them/) – YetAnotherUser

+0

'readonly' solo afecta al operador de asignación. No tiene una semántica tan fuerte como la palabra clave 'const' de C++. –

Respuesta

102

Si va a utilizar estructuras, es una buena práctica hacerlas inmutables.

Hacer que todos los campos sean solo de lectura es una excelente manera de ayudar a (1) documentar que la estructura es inmutable, y (2) evitar mutaciones accidentales.

Sin embargo, hay una arruga, que en realidad en una extraña coincidencia que estaba planeando en los blogs sobre la próxima semana. Es decir: solo en un campo struct es una mentira. Uno espera que un campo de solo lectura no pueda cambiar, pero por supuesto que sí. "readonly" en un campo struct es la declaración que escribe cheques sin dinero en su cuenta. Una estructura no posee su almacenamiento, y es ese almacenamiento el que puede mutar.

Por ejemplo, tomemos su estructura:

public struct Pair 
{ 
    public readonly int x; 
    public readonly int y; 
    public Pair(int x, int y) 
    { 
     this.x = x; 
     this.y = y; 
    } 
    public void M(ref Pair p) 
    { 
     int oldX = x; 
     int oldY = y; 
     // Something happens here 
     Debug.Assert(x == oldX); 
     Debug.Assert(y == oldY); 
    } 
} 

¿Hay algo que le puede pasar a "algo que ocurre aquí" que hace que las afirmaciones de depuración para ser violados? Por supuesto.

public void M(ref Pair p) 
    { 
     int oldX = this.x; 
     int oldY = this.y; 
     p = new Pair(0, 0); 
     Debug.Assert(this.x == oldX); 
     Debug.Assert(this.y == oldY); 
    } 
... 
    Pair myPair = new Pair(10, 20); 
    myPair.M(ref myPair); 

¿Y ahora qué sucede? ¡La afirmación es violada! "this" y "p" se refieren a la misma ubicación de almacenamiento. La ubicación de almacenamiento está mutada, por lo que los contenidos de "esto" están mutados porque son la misma cosa. La estructura no es capaz de aplicar la función de solo lectura de xey debido a que la estructura no es propietaria del almacenamiento; el almacenamiento es una variable local que puede mutar libremente tanto como lo desee.

No se puede confiar en el invariante que un campo de sólo lectura en una estructura no se observa a cambiar; Lo único en lo que puede confiar es que no puede escribir código que lo cambie directamente. Pero con un pequeño trabajo furtivo como este, puedes cambiarlo indirectamente, todo lo que quieras.

Ver también excelente artículo en el blog de Joe Duffy en este tema:

http://joeduffyblog.com/2010/07/01/when-is-a-readonly-field-not-readonly/

+16

Es increíble la cantidad de casos extremos que demuestras que me hacen pensar: "¿Quién incluso * haría * eso?" –

+3

Descubrí que puedes hacer el mismo tipo de cosas con 'StructLayout.Explicit' cuando estaba depurando el código de otra persona. –

+1

@Jim: Correcto; puede superponer un campo de solo lectura y un campo de lectura y escritura en el mismo almacenamiento sin problemas. Es extraño, pero es legal. –

5

Eso lo haría inmutable de hecho. Supongo que será mejor que añadas un constructor.
Si todos sus miembros son inmutables también, esto lo haría completamente inmutable. Estos pueden ser clases o valores simples.

+0

Claro, simplemente lo dejé por simplicidad. – Mike

+0

¿Y si fueran clases inmutables en lugar de ints? ¿Debería estar bien también? – Mike

+0

B ^) Un constructor sería útil. A menos que siempre quisieras que x e y fueran cero. –

1

El compilador prohibirá la asignación a los campos readonly, así como a las propiedades de solo lectura.

Recomiendo usar propiedades de solo lectura principalmente por razones de interfaz pública y enlace de datos (que no funcionará en los campos). Si fuera mi proyecto, requeriría eso si la estructura/clase es pública. Si va a ser interno a un ensamblado o privado a una clase, podría pasarlo por alto al principio y refactorizarlos para propiedades de solo lectura más adelante.

+2

El compilador es capaz de detectar asignaciones a los campos 'readonly' en tiempo de compilación y generará un error de compilación si se detecta uno. – dtb

+0

Es bueno saberlo. Editado mi respuesta. –

+0

El tiempo de ejecución también aplicará la semántica 'readonly'. Es decir, si 'x' es un campo' readonly' de tipo 'MyStruct', y' s' es una instancia de 'MyStruct', entonces' dynamic d = s; d.x = 42; 'lanzará una excepción. –

Cuestiones relacionadas