c# - validar - ¿Por qué Microsoft desaconseja los campos de solo lectura con valores variables?
validar objeto null c# (6)
NO asigne instancias de tipos mutables a campos de
readonly
.
Eché un vistazo rápido al libro de Pautas de diseño del marco (páginas 161-162) y básicamente dice lo que ya ha notado. Hay un comentario adicional de Joe Duffy que explica la razón de ser de la guía:
Lo que esta guía intenta protegerte es creer que has expuesto un gráfico de objetos profundamente inmutables cuando en realidad es poco profundo, y luego escribir un código que asume que el gráfico completo es inmutable.
- Joe Duffy
Personalmente creo que la palabra clave readonly
fue mal llamada. 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 engañosas.
Creo que hubiera sido preferible si los objetos de referencia hechos readonly
fueran inmutables, y no solo la referencia, porque eso es lo que implica la palabra clave.
Para remediar esta desafortunada situación, la pauta fue inventada. 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 para declarar const-ness, C # ofrecería una libertad similar a la ofrecida por C ++, donde puede definir const
en el puntero, o en el apuntado a un objeto, o en ambos o nada.
En las Pautas de diseño para desarrollar bibliotecas de clases , Microsoft dice:
No asigne instancias de tipos mutables a campos de solo lectura.
Los objetos creados usando un tipo mutable se pueden modificar después de que se crean. Por ejemplo, las matrices y la mayoría de las colecciones son tipos mutables, mientras que Int32, Uri y String son tipos inmutables. Para los campos que tienen un tipo de referencia mutable, el modificador de solo lectura evita que se sobrescriba el valor del campo, pero no protege el tipo mutable de la modificación.
Esto simplemente reafirma el comportamiento de solo lectura sin explicar por qué es malo usar solo de forma legible. 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 "solo lectura" para indicar algo diferente a 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 eso imposibilita su uso en lugares que podrían ser útiles, por ejemplo, para mostrar que alguna relación entre dos objetos mutables permanece sin cambios durante el tiempo de 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 "No inicializar innecesariamente" de FxCop, que asume que los lectores de tu código son expertos en el idioma y debe saber que (por ejemplo) los campos bool se inicializan automáticamente a falso, y le impide proporcionar la redundancia que muestra "sí, esto se ha establecido conscientemente en falso, no me olvidé de inicializarlo".
Entonces, ante todo, ¿por qué Microsoft desaconseja el uso de readonly para referencias a tipos mutables? También me gustaría saber:
- ¿Sigues esta Guía de diseño en todo tu código?
- ¿Qué esperas cuando ves "readonly" en un pedazo de código que no escribiste?
Al final, son solo pautas. Sé con certeza que las personas en Microsoft a menudo no siguen todas las pautas.
La sintaxis que está buscando es compatible con el lenguaje C ++ / CLI:
const Example^ const obj;
El primer const hace que el objeto referenciado sea inmutable, el segundo hace que la referencia sea 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.
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 de solo lectura, 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.
Parece natural que si un campo es de solo lectura, esperaría no poder cambiar el valor ni nada que tenga que ver con él . Si supiera que Bar es un campo de solo lectura de Foo, obviamente no podría decir
Foo foo = new Foo();
foo.Bar = new Baz();
Pero puedo salirse con la suya diciendo
foo.Bar.Name = "Blah";
Si el objeto Backing Bar 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.
Estoy de acuerdo con usted por completo , y algunas veces uso readonly
en mi código para tipos de referencia mutables.
Como ejemplo: podría tener algún miembro private
o protected
, por ejemplo, una List<T>
, que utilizo dentro de los métodos de una clase en toda su gloria mutable (llamando a Add
, Remove
, etc.). Es posible que simplemente quiera poner una salvaguarda para garantizar que, sin importar nada, siempre trate 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 al uso de una propiedad con un método de set
privado. ¿Por qué? Porque readonly
significa que el valor no se puede cambiar después de la instanciación, incluso por la clase base .
En otras palabras, si tuviera esto:
protected List<T> InternalList { get; private set; }
Entonces todavía podrí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 aún sería posible).
Por otro lado, esto:
protected readonly List<T> _internalList;
Hace inequívocamente claro que _internalList
solo puede referirse a un objeto en particular (aquel para el 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
.