c# - Cómo hacer una propiedad de tipo de referencia "readonly"
properties encapsulation (8)
Tengo una Bar
clase con un campo privado que contiene el tipo de referencia Foo
. Me gustaría exponer a Foo
en una propiedad pública, pero no quiero que los consumidores de la propiedad puedan alterar a Foo
... Sin embargo, debe ser modificable internamente por Bar
, es decir, no puedo hacer el campo de forma que sea readonly
.
Entonces, lo que me gustaría es:
private _Foo;
public Foo
{
get { return readonly _Foo; }
}
... que, por supuesto, no es válido. Podría devolver un clon de Foo
(asumiendo que es IClonable
), pero esto no es obvio para el consumidor. ¿Debo cambiar el nombre de la propiedad a FooCopy
? ¿Debería ser un método GetCopyOfFoo
? ¿Qué considerarías la mejor práctica? ¡Gracias!
Desafortunadamente, no hay una forma fácil de evitar esto en C # en este momento. Puede extraer la "parte de solo lectura" de Foo
en una interfaz y dejar que su propiedad devuelva eso en lugar de Foo
.
En realidad, puedes reproducir el comportamiento de C ++ const en C #, solo tienes que hacerlo manualmente.
Sea lo que sea Foo
, la única forma en que la persona que llama puede modificar su estado es llamando métodos o estableciendo propiedades.
Por ejemplo, Foo
es del tipo FooClass
:
class FooClass
{
public void MutateMyStateYouBadBoy() { ... }
public string Message
{
get { ... }
set { ... }
}
}
Entonces, en tu caso, te alegra que consigan la propiedad del Message
, pero no la configuran, y definitivamente no estás contento con que llamen a ese método.
Así que defina una interfaz que describa lo que se les permite hacer:
interface IFooConst
{
public string Message
{
get { ... }
}
}
Hemos omitido el método de mutación y solo quedamos en la propiedad.
A continuación, agregue esa interfaz a la lista base de FooClass
.
Ahora en tu clase con la propiedad Foo
, tienes un campo:
private FooClass _foo;
Y un comprador de propiedades:
public IFooConst Foo
{
get { return _foo; }
}
Esto básicamente reproduce a mano exactamente lo que la palabra clave const
C ++ haría automáticamente. En términos de psuedo-C ++, una referencia de tipo const Foo &
es como un tipo generado automáticamente que solo incluye aquellos miembros de Foo
que fueron marcados como miembros de la const
. Traduciendo esto en alguna versión futura teórica de C #, declararías FooClass
así:
class FooClass
{
public void MutateMyStateYouBadBoy() { ... }
public string Message
{
get const { ... }
set { ... }
}
}
Realmente, todo lo que hice fue fusionar la información en IFooConst
nuevamente en FooClass
, etiquetando al único miembro seguro con una nueva palabra clave const
. Entonces, de alguna forma, agregar una palabra clave const no agregaría mucho al lenguaje, además de un enfoque formal de este patrón.
Entonces, si tuviera una referencia const
a un objeto FooClass
:
const FooClass f = GetMeAFooClass();
Solo podría llamar a los miembros de const en f
.
Tenga en cuenta que si la definición de FooClass
es pública, la persona que llama podría convertir un IFooConst
en un FooClass
. Pero también pueden hacerlo en C ++: se llama " const
const_cast<T>(const T &)
" e involucra a un operador especial llamado const_cast<T>(const T &)
.
También está la cuestión de que las interfaces no son muy fáciles de desarrollar entre las versiones de su producto. Si un tercero puede implementar una interfaz que usted defina (que son libres de hacerlo si pueden verla), entonces no podrá agregarle nuevos métodos en versiones futuras sin requerir que otros recompilen su código. Pero eso es solo un problema si está escribiendo una biblioteca extensible para que otros la construyan. Tal vez una función incorporada const
resolvería este problema.
Hacer una copia, un ReadOnlyCollection
o que Foo
sea inmutable suelen ser las tres mejores rutas, como ya has especulado.
A veces prefiero los métodos en lugar de las propiedades cada vez que hago algo más importante que simplemente devolver el campo subyacente, dependiendo de la cantidad de trabajo involucrado. Los métodos implican que algo está sucediendo y generan más de una bandera para los consumidores cuando están usando su API.
La "clonación" de los objetos Foo que recibes y devuelves es una práctica normal llamada copia defensiva . A menos que haya algún efecto secundario invisible a la clonación que sea visible para el usuario, no hay absolutamente ninguna razón para NO hacer esto. A menudo es la única manera de proteger los datos privados internos de sus clases, especialmente en C # o Java, donde la idea de C ++ de const
no está disponible. (Por ejemplo, debe hacerse para crear correctamente objetos verdaderamente inmutables en estos dos idiomas).
Solo para aclarar, los posibles efectos secundarios serían cosas como que su usuario (razonablemente) espera que se devuelva el objeto original, o que Foo mantenga algún recurso que no se clonará correctamente. (En cuyo caso, ¿qué está haciendo la implementación de IClonable ?!)
Para aclarar el comentario de Jon Skeet puede hacer una vista, que es una clase de contenedor inmutable para el Foo mutable. Aquí hay un ejemplo:
class Foo{
public string A{get; set;}
public string B{get; set;}
//...
}
class ReadOnlyFoo{
Foo foo;
public string A { get { return foo.A; }}
public string B { get { return foo.B; }}
}
Parece que buscas el equivalente de "const" de C ++. Esto no existe en C #. No hay forma de indicar que los consumidores no pueden modificar las propiedades de un objeto, pero algo más puede hacerlo (suponiendo que los miembros de la mutación sean públicos, por supuesto).
Puede devolver un clon del Foo como se sugiere, o posiblemente una vista en el Foo, como lo hace ReadOnlyCollection para las colecciones. Por supuesto, si pudieras hacer de Foo
un tipo inmutable, eso haría la vida más simple ...
Tenga en cuenta que hay una gran diferencia entre hacer que el campo sea de solo lectura y hacer que el objeto sea inmutable.
Actualmente, el tipo en sí mismo podría cambiar las cosas en ambos sentidos. Podría hacer:
_Foo = new Foo(...);
o
_Foo.SomeProperty = newValue;
Si solo necesita poder hacer el segundo, el campo podría ser de solo lectura pero todavía tiene el problema de que las personas que buscan la propiedad puedan mutar el objeto. Si solo necesita hacer lo primero, y en realidad Foo
ya es inmutable o puede ser inmutable, solo puede proporcionar una propiedad que solo tenga el "captador" y todo irá bien.
Es muy importante que comprenda la diferencia entre cambiar el valor del campo (para hacer que se refiera a una instancia diferente) y cambiar el contenido del objeto al que hace referencia el campo.
Si no quieres que nadie ligue con tu estado ... ¡no lo expongas! Como han dicho otros, si algo necesita ver su estado interno, proporcione una representación inmutable de él. Alternativamente, haga que los clientes le digan que haga algo (Google para "decir no preguntar"), en lugar de hacerlo ellos mismos.
Estaba pensando en cosas de seguridad similares. Probablemente haya una manera. Muy claro pero no corto. La idea general es bastante simple. Sin embargo, siempre encontré algunas formas, así que nunca lo probé. Pero podrías verificarlo, quizás funcione para ti.
Esto es un pseudo código, pero espero que la idea detrás de esto esté clara
public delegate void OnlyRuller(string s1, string s2);
public delegate void RullerCoronation(OnlyRuller d);
class Foo {
private Foo();
public Foo(RullerCoronation followMyOrders) {
followMyOrders(SetMe);
}
private SetMe(string whatToSet, string whitWhatValue) {
//lot of unclear but private code
}
}
Por lo tanto, en la clase que crea esta propiedad, usted tiene acceso al método SetMe, pero sigue siendo privado, a excepción del creador que Foo parece inmutable.
Aún así, para algo más grande que pocas propiedades, esto probablemente pronto se convierta en un gran desastre, por eso siempre preferí otras formas de encapsulación. Sin embargo, si es muy importante para usted no permitir que el cliente cambie Foo, esta es una alternativa.
Sin embargo, como dije, esto es solo teoría.