Cómo implementar una propiedad de solo lectura

80

Necesito implementar una propiedad de solo lectura en mi tipo. Además, el valor de esta propiedad se establecerá en el constructor y no se cambiará (estoy escribiendo una clase que expone comandos de IU enrutados personalizados para WPF, pero no importa).

Veo dos formas de hacerlo:

  1. class MyClass
    {
        public readonly object MyProperty = new object();
    }
    
  2. class MyClass
    {
        private readonly object my_property = new object();
        public object MyProperty { get { return my_property; } }
    }
    

Con todos estos errores de FxCop que dicen que no debería tener variables miembro públicas, parece que la segunda es la forma correcta de hacerlo. ¿Correcto?

¿Hay alguna diferencia entre una propiedad de solo obtención y un miembro de solo lectura en este caso?

Agradecería cualquier comentario / consejo / etc.

Akonsu
fuente
31
A veces deseo que la sintaxis de propiedad automática incluya una get; readonly set;opción.
Dan Bryant
@DanBryant Lo hay get; private set;, al menos.
Marc.2377
2
@ Marc.2377, en realidad, agregaron soporte hace {get;}un tiempo, lo que resuelve este problema.
Dan Bryant
@DanBryant Ah, de hecho. Debería haber leído las respuestas primero;)
Marc.2377

Respuestas:

51

Control de versiones:
creo que no hay mucha diferencia si solo está interesado en la compatibilidad de fuentes.
El uso de una propiedad es mejor para la compatibilidad binaria, ya que puede reemplazarla por una propiedad que tenga un establecedor sin romper el código compilado según su biblioteca.

Convención:
estás siguiendo la convención. En casos como este, donde las diferencias entre las dos posibilidades son relativamente menores, seguir la convención es mejor. Un caso en el que podría volver a morderlo es el código basado en la reflexión. Es posible que solo acepte propiedades y no campos, por ejemplo, un editor / visor de propiedades.

Serialización
Cambiar de un campo a una propiedad probablemente romperá muchos serializadores. Y AFAIK XmlSerializersolo serializa propiedades públicas y no campos públicos.

Uso de una propiedad automática
Otra variación común es usar una propiedad automática con un establecedor privado. Si bien esto es breve y una propiedad, no impone la condición de solo lectura. Por eso prefiero los otros.

El campo de solo lectura se autodocumenta
Sin embargo, hay una ventaja del campo:
deja claro de un vistazo en la interfaz pública que es realmente inmutable (salvo la reflexión). Mientras que en el caso de una propiedad, solo puede ver que no puede cambiarla, por lo que tendría que consultar la documentación o implementación.

Pero para ser honesto, uso el primero con bastante frecuencia en el código de la aplicación, ya que soy un vago. En las bibliotecas, normalmente soy más minucioso y sigo la convención.

C # 6.0 agrega propiedades automáticas de solo lectura

public object MyProperty { get; }

Entonces, cuando no necesita admitir compiladores más antiguos, puede tener una propiedad de solo lectura con código que es tan conciso como un campo de solo lectura.

CódigosInChaos
fuente
1
Por el momento, deseo que se agregue C # 9public type prop => get;
NetMage
66

La segunda forma es la opción preferida.

private readonly int MyVal = 5;

public int MyProp { get { return MyVal;}  }

Esto asegurará que MyValsolo se pueda asignar en la inicialización (también se puede configurar en un constructor).

Como ha señalado, de esta manera no está exponiendo a un miembro interno, lo que le permite cambiar la implementación interna en el futuro.

Oded
fuente
Gracias. la primera opción también asegura lo mismo. ¿Cuál crees que es la razón por la que esta es la opción preferida?
Akonsu
¿Hay alguna razón por la que no deberíamos usar public int MyProp { get; private set; }? Sé que no es de solo lectura, pero está bastante cerca.
Mike Hofer
3
@Mike Hofer: dado que int se declara como de solo lectura , no puede cambiarlo fuera de un constructor.
Oded el
5
@Mike Hofer: realmente depende de cuál sea la intención ... La versión que escribí expone un miembro interno cuyo valor no puede cambiar después de la inicialización. El suyo expone un miembro cuyo valor puede cambiar después de la inicialización. Realmente depende de lo que quieras ... El mío es de solo lectura, como en, no se puede cambiar en absoluto después de init, el tuyo es de solo lectura, como en, por cualquier clase externa.
Oded
1
@Oded - Puedo aceptar eso. Es una diferencia sutil pero importante. Puedo ver dónde sería útil si quisiera exponer una propiedad que se comportara como una constante tanto interna como externamente. El mío ciertamente no haría eso.
Mike Hofer
49

Con la introducción de C # 6 (en VS 2015), ahora puede tener solo getpropiedades automáticas, en las que el campo de respaldo implícito es readonly( es decir, los valores se pueden asignar en el constructor pero no en otro lugar):

public string Name { get; }

public Customer(string name)  // Constructor
{
    Name = name;
}

private void SomeFunction()
{
    Name = "Something Else";  // Compile-time error
}

Y ahora también puede inicializar propiedades (con o sin setter) en línea:

public string Name { get; } = "Boris";

Volviendo a la pregunta, esto le brinda las ventajas de la opción 2 (el miembro público es una propiedad, no un campo) con la brevedad de la opción 1.

Desafortunadamente, no proporciona una garantía de inmutabilidad al nivel de la interfaz pública (como en el punto de @ CodesInChaos sobre la auto-documentación), porque para un consumidor de la clase, no tener un configurador es indistinguible de tener un configurador privado.

Bob Sammers
fuente
Buena información sobre la nueva sintaxis, sin embargo, puede reflejar y activar un configurador privado y / o cargar un valor en un campo de respaldo (independientemente de los modificadores de acceso). También es posible distinguir entre un configurador privado y la falta de un configurador en ejecución -tiempo usando la reflexión.
Shaun Wilson
1
@Shaun: buen punto: ¡hay muchas cosas que puedes hacer con la reflexión! Algunas de las posibilidades probablemente van en contra de la intención del programador original o incluso de los diseñadores del lenguaje, pero ¿estás diciendo que los readonlycampos se pueden alterar mediante la reflexión (no sé la respuesta, pero parece problemático)?
Bob Sammers
2
No estaba diciendo eso en particular, no, pero la respuesta es: Sí ... puedes. gist.github.com/wilson0x4d/a053c0fd57892d357b2c Si cree que eso es problemático, espere hasta que sepa que todos los sistemas operativos del planeta tienen un mecanismo en el que un proceso puede leer / escribir la memoria de cualquier otro proceso (dado el privilegio de ejecución suficiente, es decir.) Esta es la razón por la que ningún sistema basado en software puede ser realmente seguro, pero, estoy divagando, esto no tiene mucho que ver con la pregunta original :) ¡incluso si es interesante!
Shaun Wilson
Recomiendo leer el artículo detallado también el artículo de
BillWagner
13

Puedes hacerlo:

public int Property { get { ... } private set { ... } }
Nadie
fuente
8
Sí, puede, pero con esta técnica solo garantiza que los consumidores de la clase no puedan modificar la propiedad, no que permanecerá constante durante la vida útil del objeto.
Bob Sammers
Bob: Si los consumidores de la clase no pueden modificar la propiedad, entonces la propiedad es "readOnly", ¿no es así?
El Bayames
@ElBayames No puedes cambiar la referencia, pero puedes cambiar los internos. Considere cuándo expone un objeto con estado interno. Puede recuperar fácilmente el objeto con el método get expuesto y luego cambiar las propiedades internas de ese objeto. Esa no es una verdadera lectura de solo lectura.
Matthew S
5

Estoy de acuerdo en que es preferible la segunda forma. La única razón real para esa preferencia es la preferencia general de que las clases .NET no tengan campos públicos. Sin embargo, si ese campo es de solo lectura, no veo cómo habría objeciones reales aparte de la falta de coherencia con otras propiedades. La diferencia real entre un campo de solo lectura y una propiedad de solo obtención es que el campo de solo lectura proporciona una garantía de que su valor no cambiará durante la vida del objeto y una propiedad de solo obtención no lo hace.

Eric Mickelsen
fuente
4

Se prefiere el segundo método debido a la encapsulación. Ciertamente, puede hacer que el campo de solo lectura sea público, pero eso va en contra de los modismos de C # en los que tiene acceso a datos a través de propiedades y no de campos.

El razonamiento detrás de esto es que la propiedad define una interfaz pública y si la implementación de respaldo a esa propiedad cambia, no terminas rompiendo el resto del código porque la implementación está oculta detrás de una interfaz.

Joshua Rodgers
fuente
1

En C # 9, Microsoft introducirá una nueva forma de establecer propiedades solo en la inicialización usando el init;método así:

public class Person
{
  public string firstName { get; init; }
  public string lastName { get; init; }
}

De esta forma, puede asignar valores al inicializar un nuevo objeto:

var person = new Person
{
  firstname = "John",
  lastName = "Doe"
}

Pero más adelante, no puedes cambiarlo:

person.lastName = "Denver"; // throws a compiler error
Mekroebo
fuente