¿Garantizar la inmutabilidad es una justificación para exponer un campo en lugar de una propiedad?

10

La guía general para C # es usar siempre una propiedad sobre un campo público. Esto tiene sentido: al exponer un campo, está exponiendo muchos detalles de implementación. Con una propiedad, encapsula ese detalle para que quede oculto del código consumidor y los cambios de implementación se desacoplan de los cambios de interfaz.

Sin embargo, me pregunto si a veces hay una excepción válida a esta regla cuando se trata con la readonlypalabra clave. Al aplicar esta palabra clave a un campo público, ofrece una garantía adicional: inmutabilidad. Esto no es solo un detalle de implementación, la inmutabilidad es algo en lo que un consumidor podría estar interesado. El uso de un readonlycampo lo hace parte del contrato público y algo que no se puede romper con futuros cambios o herencias sin tener que modificar la interfaz pública. Eso es algo que una propiedad no puede ofrecer.

Entonces, ¿garantizar la inmutabilidad es una razón legítima para elegir un readonlycampo sobre una propiedad en algunos casos?

(Para aclarar, ciertamente no digo que siempre debas hacer esta elección solo porque el campo sea inmutable en este momento, solo cuando tenga sentido como parte del diseño de la clase y su uso previsto incluya la inmutabilidad en su contrato. Me interesan principalmente las respuestas que se centran en si esto puede estar justificado, en lugar de casos específicos en los que no lo está, como cuando necesita que el miembro esté en una interfaceo desea realizar una carga diferida).

Ben Aaronson
fuente
1
@gnat Solo para aclarar, mediante "Propiedad ReadOnly" están utilizando la terminología VB.NET, lo que significa una propiedad sin setter, no un campo de solo lectura. Para los fines de mi pregunta, las dos opciones que compara esa pregunta son equivalentes: ambas usan propiedades.
Ben Aaronson

Respuestas:

6

Los campos públicos de solo lectura estática están bien. Se recomiendan para ciertas situaciones, como cuando desea un objeto constante con nombre pero no puede usar la constpalabra clave.

Los campos de miembros de solo lectura son un poco más complicados. No se obtiene mucha ventaja sobre una propiedad sin un setter público, y hay una gran desventaja: los campos no pueden ser miembros de interfaces. Por lo tanto, si decide refactorizar su código para operar contra una interfaz en lugar de un tipo concreto, tendrá que cambiar sus campos de solo lectura a propiedades con captadores públicos.

Una propiedad pública no virtual sin un setter protegido proporciona protección contra la herencia, a menos que las clases heredadas tengan acceso al campo de respaldo. Puede hacer cumplir ese contrato por completo en la clase base.

No ser capaz de cambiar la "inmutabilidad" del miembro sin cambiar la interfaz pública de la clase no es una gran barrera en este caso. Por lo general, si desea cambiar un campo público en una propiedad, se realiza sin problemas, excepto que hay dos casos con los que tiene que lidiar:

  1. Cualquier cosa que escriba a un miembro de ese objeto, como: object.Location.X = 4solo es posible si Locationes un campo. Sin embargo, esto no es relevante para un campo de solo lectura, porque no puede modificar una estructura de solo lectura. (Esto supone que Locationes un tipo de valor; de lo contrario, este problema no se aplicaría de todos modos porque readonlyno protege contra ese tipo de cosas).
  2. Cualquier llamada a un método que pasa el valor como outo ref. Una vez más, esto no es realmente relevante para un campo de sólo lectura, porque outy refparámetros tienden a ser modificado, y es un error de compilación de todos modos para pasar un valor de sólo lectura como fuera o ref.

En otras palabras, aunque la conversión de un campo de solo lectura a una propiedad de solo lectura rompe la compatibilidad binaria, otro código fuente solo necesitaría ser recompilado sin ningún cambio para manejar este cambio. Eso hace que la garantía sea realmente fácil de romper.

No veo ninguna ventaja en el readonlycampo de miembros, y la imposibilidad de tener ese tipo de cosas en una interfaz es una desventaja suficiente para mí que no lo usaría.

Erik
fuente
Por interés, ¿por qué los campos de solo lectura estáticos públicos están bien? ¿Es solo porque descartar los métodos de instancia elimina la mayoría de los casos de uso para propiedades en campos inmutables (como conteo de aciertos, carga diferida, etc.)?
Ben Aaronson
La principal desventaja de un campo público de solo lectura frente a una propiedad de solo lectura ha desaparecido: los miembros estáticos no pueden ser parte de las interfaces, por lo que están en pie de igualdad. Una propiedad estática de solo lectura necesita almacenamiento que se inicializa o necesita reconstruir su valor de retorno cada vez que se llama, mientras que un campo de solo lectura tendrá una sintaxis más simple y será inicializado por el constructor estático. Por lo tanto, creo que un campo público de solo lectura estática tiene el potencial de una mayor optimización por parte del JIT sin ninguna de las desventajas que normalmente tendría al exponer un campo.
Erik
2

Desde mi experiencia, la readonlypalabra clave no es la magia que promete.

Por ejemplo, tiene una clase donde el constructor solía ser simple. Dado el uso (y el hecho de que algunas de las propiedades / campos de clase son inmutables después de la construcción), puede pensar que no importa, por lo que usó la readonlypalabra clave.

Más tarde, la clase se vuelve más compleja, y también lo es su constructor. (Digamos que esto sucede en un proyecto que es experimental o tiene una velocidad lo suficientemente alta fuera del alcance / control pero necesita que funcione de alguna manera). Encontrará que los campos que readonlysolo se pueden modificar dentro del constructor: usted no puede modificarlo en métodos incluso si ese método solo se llama desde un constructor.

Esta limitación técnica ha sido reconocida por los diseñadores de C #, y podría corregirse en una versión futura. Pero hasta que se solucione, el uso de readonlyes más restrictivo y puede fomentar un estilo de codificación incorrecto (poner todo en el método del constructor).

A modo de recordatorio, ni la readonlypropiedad de establecimiento público no ofrece ninguna garantía de un "objeto completamente inicializado". En otras palabras, es el diseñador de una clase quien decide qué significa "completamente inicializado" y cómo protegerlo contra el uso involuntario, y dicha protección generalmente no es infalible, lo que significa que alguien podría encontrar una forma de evitarlo.

rwong
fuente
1
Prefiero que los constructores sean simples: consulte brendan.enrick.com/post/… , blog.ploeh.dk/2011/03/03/InjectionConstructors debería ser simple , de hecho, solo recomienda un buen estilo de codificación
AlexFoxGill
1
Un constructor puede pasar a un readonlycampo como una outo refparámetro para otros métodos, que entonces será libre de modificar como les parezca.
supercat
1

Los campos son SIEMPRE detalles de implementación. No desea exponer los detalles de su implementación.

Un principio fundamental de la ingeniería de software es que no sabemos cuáles serán los requisitos en el futuro. Si bien su readonlycampo puede ser bueno hoy, no hay nada que sugiera que será parte de los requisitos de mañana. ¿Qué pasa si mañana es necesario implementar un contador de visitas para determinar cuántas veces se accede a ese campo? ¿Qué sucede si necesita permitir que las subclases anulen el campo?

Las propiedades son tan fáciles de usar y son mucho más flexibles que los campos públicos que no puedo pensar en un solo caso de uso en el que elegiría c # como mi idioma y usaría campos públicos de solo lectura en una clase. Si el rendimiento fuera tan ajustado que no pudiera recibir el golpe de la llamada al método adicional, probablemente estaría usando un idioma diferente.

El hecho de que podamos hacer algo con el idioma no significa que debamos hacer algo con el idioma.

De hecho, me pregunto si los diseñadores de idiomas lamentan haber hecho públicos los campos. No recuerdo la última vez que incluso consideré usar campos públicos no constantes en mi código.

Stephen
fuente
1
Entiendo la importancia de construir una flexibilidad futura, pero seguramente si escribo una clase como, por ejemplo, Rationalcon público, inmutable Numeratory Denominatormiembros, podría estar extremadamente seguro de que esos miembros no requerirán carga lenta o un contador de visitas o ¿similar?
Ben Aaronson
¿Pero puede estar seguro de que la implementación utilizando floattipos para Numeratory Denominatorno tendrá que cambiarse doubles?
Stephen
1
Bueno, er, no, definitivamente no deberían ser ni floattampoco double. Deberían ser int, longo BigInteger. Tal vez esos necesiten cambiar ... pero si es así también tendrían que cambiar en la interfaz pública, por lo que el uso de propiedades realmente no agrega encapsulación en torno a ese detalle.
Ben Aaronson
-3

No. No es una justificación.

Usar el solo lectura como garantía de inmutabilidad, no como justificación, es más como un hack.

Solo lectura significa que el valor lo asigna un constructor o cuando se define la variable.

Creo que será una mala práctica

Si realmente desea garantizar la inmutabilidad, use una interfaz que tenga más ventajas que desventajas.

Ejemplo de interfaz simple: ingrese la descripción de la imagen aquí

Pablo Granados
fuente
1
"usar una interfaz" ¿Cómo?
Ben Aaronson