2010-12-09 14 views
5

Estoy anulando una propiedad en mi clase derivada que me gustaría que fuera de solo lectura. El compilador de C# no me permite cambiar los modificadores de acceso, por lo que debe permanecer público.Hacer la propiedad readonly en la clase derivada

¿Cuál es la mejor manera de hacerlo? ¿Debería lanzar un InvalidOperationException en set { }?

+0

Necesitamos más contexto que pienso. – Noldorin

Respuesta

8

Tener el setter lanzar un InvalidOperationException en una clase derivada viola el Liskov Subsitution Principle. Básicamente hace que el uso del setter sea contextual para el tipo de la clase base que esencialmente elimina el valor del polimorfismo.

Su clase derivada debe respetar el contrato de su clase base. Si el colocador no es apropiado en todas las circunstancias, entonces no pertenece a la clase base.

Una forma de evitar esto es romper la jerarquía un poco.

class C1 { 
    public virtual int ReadOnlyProperty { get; } 
} 
class C2 { 
    public sealed override int ReadOnlyProperty { 
    get { return Property; } 
    } 
    public int Property { 
    get { ... } 
    set { ... } 
    } 
} 

Su tipo que está teniendo problemas con podían heredar C1 en este escenario y el resto podría cambiar a derivar de C2

+0

Entonces, si la clase base tiene una propiedad 'IsReadOnly' y arroja su propia excepción, entonces ¿está bien? Así es como funciona la herencia 'NameObjectCollectionBase'>' NameValueCollection'> 'HttpValueCollection', que es utilizada por' Request.Param' de 'Page'. Entiendo el razonamiento detrás del principio, pero en el caso de 'NameObjectCollectionBase', contradice su afirmación:' Si el setter no es apropiado en todas las circunstancias, entonces no pertenece a la clase base. Desafortunadamente estoy usando a. Clase NET que no implementó esto (por una buena razón). Creo que tiene sentido en mi caso. –

+1

@Nelson que tiene la clase base con 'IsReadOnly' es ... un compromiso en el mejor de los casos. Personalmente creo que es un patrón terrible y BCL hubiera sido mucho mejor si las colecciones estuvieran correctamente divididas en interfaces/tipos mutables y de solo lectura. – JaredPar

+0

Ok, me haré más específico. Me estoy derivando de 'ListView' de ASP.NET. Vamos a llamarlo 'MyCustomList'. Quiero todas las características/plantillas de 'ListView', pero dejo que' MyCustomList' maneje los datos, asigne 'DataSource/Id' y evite que' DataSource/Id' se modifique. Creo que lanzar una excepción es un buen compromiso frente a la implementación de toda la funcionalidad 'ListView'. –

3

puede ocultar la implementación original y devolver la implementación base:

class Foo 
{ 
    private string _someString; 
    public virtual string SomeString 
    { 
     get { return _someString; } 
     set { _someString = value; } 
    } 
} 

class Bar : Foo 
{ 
    public new string SomeString 
    { 
     get { return base.SomeString; } 
     private set { base.SomeString = value; } 
    } 
} 

namespace ConsoleApplication3 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Foo f = new Foo(); 
      f.SomeString = "whatever"; // works 
      Bar b = new Bar(); 
      b.SomeString = "Whatever"; // error 
     } 
    } 
} 

Sin embargo ,. como Jared ha aludido, esta es una situación extraña. ¿Por qué no hace que su setter sea privado o esté protegido en la clase base para empezar?

+0

Excepto que si lo hace "Foo f2 = b; f.SomeString =" eeba geeba! ";" De su ejemplo, el compilador lo permite. – mjfgates

+0

@mjfgates: supongo que te refieres a 'Foo f = new Bar(); f.SomeString = "blah" '? Esa es la razón por la que no hice esta ruta. No puedo cambiar la clase base ya que provengo de un control web ASP.NET estándar. –

+0

No, quise decir lo que escribí. En ese caso, está llamando a Foo.SomeString porque SomeString no es polimórfico en Bar, está ocultando la implementación de Foo. –

0

No tiene permitido ocultar los miembros públicos de su clase base en C#, porque alguien podría tomar una instancia de su clase derivada, rellenarla en una referencia a la clase base y acceder a la propiedad de esa manera.

Si quiere hacer una clase que proporcionó la mayoría, pero no toda, la interfaz de otra clase, tendrá que usar la agregación. No herede de la "clase base", solo incluya una en el valor "derivado" por valor y proporcione sus propias funciones miembro para las porciones de la funcionalidad de "clase base" que desea exponer. Esas funciones pueden simplemente reenviar las llamadas a las funciones apropiadas de "clase base".

0

Simplemente defina la propiedad con nueva palabra clave y no proporcione el método Setter en la clase derivada. Esto también ocultará la propiedad de la clase base, sin embargo, como se mencionó anteriormente, uno puede acceder a la propiedad de la clase base asignándola a una instancia de clase base. Eso es polimorfismo.

0

Hay algunos casos en los que el patrón que describes es justificable. Por ejemplo, puede ser útil tener una clase abstracta MaybeMutableFoo, de la cual derivan los subtipos MutableFoo y ImmutableFoo. Las tres clases incluyen una propiedad IsMutable y los métodos AsMutable(), AsNewMutable() y AsImmutable().

En esa situación, sería totalmente apropiado para el MaybeMutableFoo exponer una propiedad de lectura-escritura, si su contrato especifica explícitamente que el colocador puede no funcionar a menos que IsMutable devuelva verdadero. Un objeto que tenga un campo de tipo MaybeMutableFoo que contenga una instancia de ImmutableFoo podría estar perfectamente satisfecho con esa instancia a menos que o hasta que tuviera que escribir en ella, con lo cual reemplazaría el campo con un objeto devuelto por AsMutable() y luego lo usaría como un foo mutable (sabría que era mutable, ya que simplemente lo había reemplazado).Tener MaybeMutableFoo incluir un setter evitaría la necesidad de hacer futuros typecasts en el campo una vez que se hizo para referirse a una instancia mutable.

La mejor manera de permitir un patrón así es evitar la implementación de propiedades virtuales o abstractas, pero en su lugar implementar propiedades no virtuales cuyos captadores y establecedores se encadenan a métodos virtuales o abstractos. Si uno tiene una clase base

public class MaybeMutableFoo 
{ 
    public string Foo {get {return Foo_get();} set {Foo_set(value);} 
    protected abstract string Foo_get(); 
    protected abstract void Foo_set(string value}; 
} 

entonces una clase derivada ImmutableFoo puede declarar:

new public string Foo {get {return Foo_get();} 

para hacer su Foo propiedad de solo lectura sin interferir con la capacidad de codificar las anulaciones por lo abstracto Foo_get() y Foo_set() métodos. Tenga en cuenta que el reemplazo de solo lectura de Foo no cambia el comportamiento de la propiedad get; la versión de solo lectura encadena al mismo método de clase base que la propiedad de clase base. Hacer las cosas de esta manera asegurará que haya un punto de parche para cambiar el captador de propiedades, y otro para cambiar el colocador, y esos puntos de parche no cambiarán, incluso si la propiedad misma se redefine.

1

Creo que esta situación la mejor solución es nueva palabra clave

public class Aclass {   
    public int ReadOnly { get; set; } 
} 

public class Bclass : Aclass {   
    public new int ReadOnly { get; private set; } 
} 
+0

Con la nueva palabra clave puede ocultar la base obtener y establecer accesorios – Dimitrios

Cuestiones relacionadas