c# - ¿Puedo anular con tipos derivados?
.net inheritance (8)
No. C # no es compatible con esta idea (se llama "covarianza de tipo de retorno").
De la Wikipedia:
En el lenguaje de programación C #, se agregó la compatibilidad con la covarianza de tipo retorno y la contravariancia de parámetro para los delegados en la versión 2.0 del lenguaje. No se admite la covarianza ni la contravarianza para anular el método.
Puede volver a declarar (nuevo), pero no puede volver a declarar y anular al mismo tiempo (con el mismo nombre). Una opción es usar un método protegido para ocultar los detalles; esto permite tanto el polimorfismo como la ocultación al mismo tiempo:
Las mejores soluciones serían usar genéricos:
public class MyClass<T> where T: Person
{
public virtual T SomePropertyNameA
{
get { return ...; }
}
}//Then the Father and Child are generic versions of the same class
Por lo que sé, no es posible hacer lo siguiente en C # 2.0
public class Father
{
public virtual Father SomePropertyName
{
get
{
return this;
}
}
}
public class Child : Father
{
public override Child SomePropertyName
{
get
{
return this;
}
}
}
Soluciono el problema creando la propiedad en la clase derivada como "nueva", pero, por supuesto, eso no es polimórfico.
public new Child SomePropertyName
¿Hay alguna solución en 2.0? ¿Qué hay de las características en 3.5 que abordan este asunto?
De la Wikipedia :
En el lenguaje de programación C #, se agregó la compatibilidad con la covarianza de tipo retorno y la contravariancia de parámetro para los delegados en la versión 2.0 del lenguaje. No se admite la covarianza ni la contravarianza para anular el método.
Sin embargo, no dice explícitamente nada sobre la covarianza de las propiedades.
Esto es lo más cerca que podría llegar (hasta ahora):
public sealed class JustFather : Father<JustFather> {}
public class Father<T> where T : Father<T>
{ public virtual T SomePropertyName
{ get { return (T) this; }
}
}
public class Child : Father<Child>
{ public override Child SomePropertyName
{ get { return this; }
}
}
Sin la clase JustFather
, no podría crear una instancia de un Father<T>
menos que fuera algún otro tipo derivado.
Esto no es posible en ningún lenguaje .NET debido a problemas de seguridad de tipo. En los lenguajes seguros para tipos, debe proporcionar covarianza para los valores de retorno y contravariancia para los parámetros. Toma este código:
class B {
S Get();
Set(S);
}
class D : B {
T Get();
Set(T);
}
Para los métodos Get
, covarianza significa que T
debe ser S
o un tipo derivado de S
De lo contrario, si tuviera una referencia a un objeto de tipo D
almacenado en una variable escrita con B
, cuando llamó a B.Get()
no obtendría un objeto representable como un S
rompa el sistema de tipo.
Para los métodos Set
, la contravarianza significa que T
debe ser S
o un tipo del que S
deriva. De lo contrario, si tuviera una referencia a un objeto de tipo D
almacenado en una variable con tipo B
, cuando llamó a B.Set(X)
, donde X
era de tipo S
pero no de tipo T
, D::Set(T)
haría obtener un objeto de un tipo que no esperaba
En C #, hubo una decisión consciente de no permitir el cambio de tipo cuando se sobrecargaban las propiedades, incluso cuando solo tenían uno de los pares getter / setter, porque de lo contrario tendrían un comportamiento muy inconsistente ( "¿Quiere decir que puedo cambiar el tipo en el uno con un getter, pero no uno con getter y setter? ¿Por qué no?!? " - Newbie Universo Alternativo Anónimo).
No, pero puedes usar genéricos en 2 y más:
public class MyClass<T> where T: Person
{
public virtual T SomePropertyName
{
get
{
return ...;
}
}
}
Entonces Padre e hijo son versiones genéricas de la misma clase
No. C # no es compatible con esta idea (se llama "covarianza de tipo de retorno"). Sin embargo, puedes hacer esto:
public class FatherProp
{
}
public class ChildProp: FatherProp
{
}
public class Father
{
public virtual FatherProp SomePropertyName
{
get
{
return new FatherProp();
}
}
}
public class Child : Father
{
public override FatherProp SomePropertyName
{
get
{
// override to return a derived type instead
return new ChildProp();
}
}
}
es decir, utiliza el contrato definido por la clase base, pero devuelve un tipo derivado. Hice una muestra más detallada para aclarar este punto: devolver "esto" de nuevo no cambiaría nada.
Es posible (pero desordenado) probar el objeto devuelto para su tipo real (es decir, "si algún objeto es ChildProp"), pero es mejor llamar a un método virtual que hace lo correcto para su tipo.
El método virtual de la clase base (en este caso, propiedad virtual) no solo tiene una implementación, sino que también define un contrato: que una clase hija puede suministrar una implementación diferente de SomePropertyName si cumple este contrato (es decir, SomePropertyName devuelve un objeto de tipo " PadreProp "). Devolver un objeto de tipo "ChildProp" derivado de "FatherProp" cumple este contrato. Pero no puede cambiar el contrato en "Niño": este contrato se aplica a todas las clases descendientes de "Padre".
Si das un paso atrás y observas tu diseño más amplio, hay otros constructos de lenguaje en el kit de herramientas de C # en los que quizás también quieras pensar: genéricos o interfaces.
Puede crear una interfaz común para padre e hijo y devolver un tipo de esa interfaz.
Puede volver a declarar (nuevo), pero no puede volver a declarar y anular al mismo tiempo (con el mismo nombre). Una opción es usar un método protegido para ocultar los detalles; esto permite tanto el polimorfismo como la ocultación al mismo tiempo:
public class Father
{
public Father SomePropertyName
{
get {
return SomePropertyImpl();
}
}
protected virtual Father SomePropertyImpl()
{
// base-class version
}
}
public class Child : Father
{
public new Child SomePropertyName
{
get
{ // since we know our local SomePropertyImpl actually returns a Child
return (Child)SomePropertyImpl();
}
}
protected override Father SomePropertyImpl()
{
// do something different, might return a Child
// but typed as Father for the return
}
}