2010-05-10 9 views
34

En el Design Guidelines for Developing Class Libraries, Microsoft dicen:¿Por qué Microsoft desaconseja los campos readonly con valores mutables?

Do not assign instances of mutable types to read-only fields.

The objects created using a mutable type can be modified after they are created. For example, arrays and most collections are mutable types while Int32, Uri, and String are immutable types. For fields that hold a mutable reference type, the read-only modifier prevents the field value from being overwritten but does not protect the mutable type from modification.

Esto simplemente reitera el comportamiento de sólo lectura sin explicar por qué es malo usar sólo lectura. La implicación parece ser que muchas personas no entienden lo que "solo lee" y esperan erróneamente que los campos de solo lectura sean profundamente inmutables. En efecto, aconseja utilizar "readonly" como documentación de código que indica la inmutabilidad profunda, a pesar de que el compilador no tiene forma de imponer esto, y no permite su uso para su función normal: garantizar que el valor del campo no cambie después de el objeto ha sido construido.

Me siento incómodo con esta recomendación de usar "readonly" para indicar algo distinto de su significado normal entendido por el compilador. Creo que alienta a las personas a malinterpretar el significado de "solo lectura" y, además, a esperar que signifique algo que el autor del código podría no pretender. Siento que no se puede usar en lugares en los que podría ser útil, p. para mostrar que alguna relación entre dos objetos mutables permanece sin cambios durante la vida de uno de esos objetos. La noción de suponer que los lectores no entienden el significado de "solo lectura" también parece estar en contradicción con otros consejos de Microsoft, como la regla "Do not initialize unnecessarily" de FxCop, que asume que los lectores de su código son expertos en el lenguaje y deben saberlo (por ejemplo) los campos bool se inicializan automáticamente a falso, y te impide proporcionar la redundancia que muestra "sí, esto se ha establecido conscientemente en falso, no me olvidé de inicializarlo".

Por lo tanto, ante todo, ¿por qué Microsoft desaconseja el uso de readonly para referencias a tipos mutables? También me gustaría saber:

  • ¿Sigues esta directriz de diseño en todos tus códigos?
  • ¿Qué esperas cuando ves "readonly" en un código que no has escrito?
+2

100% de acuerdo. Pauta típica de Microsoft. \ * flush \ * –

+0

No puedo decir que estoy libre de prejuicios. ;) Sentí que la respuesta de Anthony, aunque precisa, no decía nada que no estuviera ya indicado en la guía: las referencias de solo lectura a los objetos mutables son malas ... porque el objeto es mutable. Por otro lado, su respuesta al menos dio más detalles sobre por qué es posible que desee tener referencias de solo lectura para objetos mutables. Ahora que lo veo, quizás la respuesta de Stakx es, estrictamente hablando, la respuesta más precisa a la pregunta que originalmente hice. – Weeble

Respuesta

18

I totalmente de acuerdo contigo, y yo hacer utilizan a veces readonly en mi código para los tipos de referencia mutables.

A modo de ejemplo: Podría tener algún private o protected miembro - decir, un List<T> - que uso dentro de los métodos de una clase en todo su esplendor mutable (llamando Add, Remove, etc.). Es posible que simplemente desee poner una salvaguardia en su lugar para garantizar que, sin importar qué, siempre estoy tratando con el mismo objeto. Esto me protege tanto a mí como a otros desarrolladores de hacer algo estúpido: asignarle un nuevo objeto.

Para mí, esta es a menudo una alternativa preferible a usar una propiedad con un método privado set. ¿Por qué? Como readonly significa , el valor no se puede cambiar después de la creación de instancias, ni siquiera en la clase base.

En otras palabras, si tuviera esto:

protected List<T> InternalList { get; private set; } 

Entonces todavía se podía establecer InternalList = new List<T>(); en cualquier punto arbitrario en el código en mi clase base. (Esto requeriría un error muy tonto de mi parte, sí, pero todavía sería posible.)

Por otro lado, esto:

protected readonly List<T> _internalList; 

Hace que sea inequívocamente claro que _internalListlata solo se refieren a un objeto en particular (aquel al que se establece _internalList en el constructor).

Así que estoy de tu lado. La idea de que uno debe abstenerse de usar readonly en un tipo de referencia mutable es frustrante para mí personalmente, ya que básicamente presupone un malentendido de la palabra clave readonly.

+0

+1, perfectamente de acuerdo, privado + solo = preocupaciones en el código. –

+0

Utilizo mucho el readonly privado en los tipos de referencia. Es perfecto para garantizar que nunca toque mis dependencias en una clase. Nunca espero que un campo de solo lectura sea inmutable. Dicho esto, tampoco entiendo el ms guidline. Creo que es mejor seguir usando la función en lugar de proteger a los desarrolladores desconocedores de las lecciones que definitivamente deberían aprender. – Console

25

Parece natural que si un campo es de sólo lectura, se puede esperar que no sea capaz de cambiar el valor o cualquier cosa que tenga que ver con ello. Si supiera que el bar era un campo de sólo lectura de Foo, podría obviamente no decir

Foo foo = new Foo(); 
foo.Bar = new Baz(); 

Pero puedo salir a decir

foo.Bar.Name = "Blah"; 

Si la barra de respaldo objeto es, de hecho, mutable. Microsoft simplemente está recomendando contra ese comportamiento sutil e intuitivo al sugerir que los campos de solo lectura estén respaldados por objetos inmutables.

+1

Dices que parece natural, pero no me parece así. Puede parecer * deseable * que existan algunos medios para especificar la inmutabilidad profunda, pero no parece * natural * que simplemente lo sea, e incluso eso no conduce obviamente a la conclusión de que debemos comportarnos * como si * fuera el verdadero significado de readonly cuando no lo es. – Weeble

2

Microsoft tiene algunos consejos tan peculiares. El otro que inmediatamente viene a la mente es no anidar tipos genéricos en miembros públicos, como List<List<int>>. Intento evitar estos constructos donde sea posible, pero ignoro el consejo de novatos cuando siento que el uso está justificado.

En cuanto a los campos readonly, trato de evitar los campos públicos como tales, en lugar de buscar propiedades. Creo que también hubo un consejo al respecto, pero lo más importante es que ahora hay casos en los que un campo no funciona mientras que una propiedad sí (en su mayoría tiene que ver con el enlace de datos y/o diseñadores visuales). Al hacer todas las propiedades de campos públicos evito cualquier problema potencial.

+0

Usted plantea un buen punto: ¿las pautas de diseño se aplican solo a los miembros no privados? Las directrices ya dicen que no se utilicen campos públicos o protegidos. Supongo que se centran en la API pública de una biblioteca, en la que los campos privados obviamente no tienen nada que ver. Ciertamente, no siento una necesidad apremiante de tener * campos públicos * mutables simplemente, ya que rara vez siento la necesidad de tener campos públicos en absoluto. – Weeble

1

Al final son solo pautas.Sé con certeza que las personas en Microsoft a menudo no siguen todas las pautas.

7

DO NOT assign instances of mutable types to readonly fields.

Tenía una mirada rápida en el marco Directrices de diseño libro (páginas 161-162), y que básicamente dice lo que ya ha notado usted mismo. Hay un comentario adicional por Joe Duffy que explica la razón de ser de la directriz:

What this guideline is trying to protect you from is believing you've exposed a deeply immutable object graph when in fact it is shallow, and then writing code that assumes the whole graph is immutable.
— Joe Duffy

Personalmente, creo que la palabra clave fue nombrado readonly mal. El hecho de que solo especifique la constidad de la referencia, y no de la constidad del objeto al que se hace referencia, crea fácilmente situaciones confusas.

creo que habría sido preferible que readonly dieron a objetos referenciados inmutable, también, y no sólo la referencia, porque es lo que implica la palabra clave.

Para remediar esta desafortunada situación, se elaboró ​​la guía.Si bien creo que su consejo es sólido desde el punto de vista humano (no siempre es obvio qué tipos son mutables y cuáles no sin buscar su definición, y la palabra sugiere una inmutabilidad profunda), a veces deseo que, cuando se trata de declarar const-ness, C# ofrecería una libertad similar a la ofrecida por C++, donde puede definir const ya sea en el puntero, o en el apuntado a un objeto, o en ambos o nada.

+1

Aunque entiendo algo de su punto, una cosa que definitivamente * no * deseo es el horrible estilo de C++ 'const'. –

+0

_ @ JS Bangs: _ Por supuesto, esto se reduce a las preferencias personales. La sintaxis de declaración de tipo de C es, en efecto, ... bueno, "especial", pero ofrece mucha flexibilidad. El 'const' de C probablemente no encajaría bien en la relativa simplicidad del sistema de tipos de C#/.NET ... pero a veces lo extraño. 'const' es una herramienta más poderosa de lo que mucha gente piensa. – stakx

+0

Upvoted, pero no estoy de acuerdo en el único punto que readonly debería hacer que los objetos referenciados sean inmutables. No veo que funcione sin un sistema aún más complejo que el const de C++: cuando accedes a un objeto a través de una referencia de solo lectura, ¿eso hace que todas sus propiedades y campos devuelvan referencias de solo lectura? ¿Qué pasa con los valores de retorno de su método? ¿Necesita una forma de declarar explícitamente los métodos const/readonly? ¿Cómo funciona todo cuando el objeto es de un tipo declarado en otro ensamblaje? – Weeble

1

La sintaxis que busca el apoyo de la lengua ++/CLI C:

const Example^ const obj; 

La primera const hace que el objeto referenciado inmutable, el segundo hace referencia inmutable. El último es equivalente a la palabra clave readonly en C#. Los intentos de evadirlo producen un error de compilación:

Test^ t = gcnew Test(); 
t->obj = gcnew Example(); // Error C3892 
t->obj->field = 42;   // Error C3892 
Example^ another = t->obj; // Error C2440 
another->field = 42; 

Sin embargo, es humo y espejos. La inmutabilidad es verificada por el compilador, no por el CLR. Otro lenguaje administrado podría modificar ambos. Cuál es la raíz del problema, el CLR simplemente no tiene soporte para eso.

Cuestiones relacionadas