2008-10-01 21 views
32

Por lo que yo sé que no es posible hacer lo siguiente en C# 2,0¿Puedo anular con tipos derivados?

public class Father 
{ 
    public virtual Father SomePropertyName 
    { 
     get 
     { 
      return this; 
     } 
    } 
} 

public class Child : Father 
{ 
    public override Child SomePropertyName 
    { 
     get 
     { 
      return this; 
     } 
    } 
} 

que solucionar el problema mediante la creación de la propiedad en la clase derivada como "nuevo", pero por supuesto que no es polimórfica.

public new Child SomePropertyName 

¿Hay alguna solución en 2.0? ¿Qué hay de las características en 3.5 que abordan este asunto?

+0

Todas las soluciones, excepto la que dice que no se puede realmente ayudar a la situación. Creo que el punto era poder tener una clase de Child (elegida como padre) y llamar SomeProperty y hacer que devolvieran un Child sin un elenco.Por supuesto que estoy adivinando el verdadero propósito como evryone else :) – mattlant

+0

A pesar de que no existe una solución perfecta, la idea es encontrar la forma más clara de implementar esto, con las cosas menos ruidosas, como los moldes, verificar los tipos usando "es" y así. –

+3

Aunque no puede hacer esto en C#, he escrito una serie de publicaciones en el blog sobre cómo hacer exactamente esto: http://www.simple-talk.com/community/blogs/simonc/archive/2010/07/14 /93495.aspx – thecoop

Respuesta

18

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. Tome 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 o bien S o un tipo derivado de S. De lo contrario, si tuviera una referencia a un objeto del tipo D almacenado en una variable ingresada en B, al llamar a B.Get(), no obtendría un objeto representable como S que rompa el sistema de tipos.

Para los métodos Set, contravarianza significa que T debe ser o bien S o un tipo que S deriva de. De lo contrario, si has tenido una referencia a un objeto de tipo D almacenado en una variable de tipo B, cuando llamó B.Set(X), donde X fue de tipo S pero no de tipo T, D::Set(T) obtendría 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 una pareja getter/setter, porque de lo contrario tendrían un comportamiento muy inconsistente ("¿Quiere decir que puedo? cambie el tipo en el que tiene un getter, pero no uno con getter y setter? ¿Por qué no?!? " - Newbie Universo Alternativo Anónimo).

+3

Su punto es válido, pero el ejemplo tenía una propiedad de solo lectura. – Anthony

+0

¡Perfectamente demostrado! –

+1

Está introduciendo una restricción aritficial que 'Get' tiene que devolver el mismo' T' que 'Set' recibe. Sin esta restricción, su argumento no se cumple. Vea esta respuesta para una mejor explicación: http://stackoverflow.com/questions/5709034/does-c-sharp-support-return-type-covariance – lex82

1

No. C# no es compatible con esta idea (se denomina "covarianza de tipo de retorno"). No obstante, usted puede 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, utilizar el contrato definido por la clase base, pero devuelven 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 da un paso atrás y observa su diseño más amplio, hay otros constructos de lenguaje en el kit de herramientas de C# en los que también puede pensar: genéricos o interfaces.

+0

No estoy seguro de que valga otra respuesta, pero en C++, puede cambiar el tipo de devolución para devolver un Niño en lugar de Padre, ya que los tipos de devolución pueden especializarse en funciones virtuales de clase derivada. Sin embargo, la única razón es porque pueden usarse como el tipo de base, por lo que es superfluo :) – workmad3

+0

Java también lo permite a partir de 1.5. Se llama covarianza de tipo retorno. –

+0

Jon Skeet: gracias por esa información, la he agregado. – Anthony

39

Puede volver a declarar (nuevo), pero no puede volver a declarar y anular al mismo tiempo (con el mismo nombre). Una opción es utilizar un método protegido para ocultar el detalle - esto permite que tanto el polimorfismo y ocultando 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 
    } 
} 
+9

Uso este truco todo el tiempo, y lo odio :) – Trillian

7

De Wikipedia:

En el lenguaje de programación C#, soporte tanto para La contravariancia para delegados de tipo de retorno y el parámetro se agregó en la versión 2.0 del idioma. Ni la covarianza ni la contravariación son compatibles con la anulación de método.

Sin embargo, no dice nada explícitamente sobre la covarianza de las propiedades.

+6

propiedades son solo dos métodos (set_Foo y get_Foo) – sehe

11

No, pero puede utilizar los genéricos en 2 y superiores:

public class MyClass<T> where T: Person 
{ 
    public virtual T SomePropertyName 
    { 
     get 
     { 
      return ...; 
     } 
    } 
} 

Entonces el padre y el niño son versiones genéricas de la misma clase

+1

No es exactamente lo mismo. Según su código, esto: Padre hijo = hijo nuevo(); Niño newChild = child.SomePropertyName; requeriría un yeso. Con el tuyo, ni siquiera compilará. –

+0

Tienes razón, no lo es. Este es solo un ejemplo bastante básico de algo similar: no se puede comparar su código con los genéricos, creo que obtendría una referencia de tipo circular. – Keith

2

Se puede crear una interfaz común para el padre y el niño y el retorno un tipo de esa interfaz.

+0

Esta es una respuesta corta y floja. Pero es la respuesta más simple que funciona en este caso, me gusta :-) +1 – Mendelt

+0

La respuesta más simple sería: Dado que Child también es del tipo Padre, simplemente puede devolver el hijo sin cambiar el tipo de devolución de la propiedad. ;-) – VVS

+0

O .. "la covarianza del tipo de retorno no existe en C#" – VVS

0

Nº C# no es compatible con esta idea (se llama "tipo de retorno covarianza").

De Wikipedia:

En el lenguaje de programación C#, apoyo tanto de tipo de retorno covarianza y el parámetro se añadió contravarianza para los delegados en la versión 2.0 del lenguaje. Ni la covarianza ni la contravariación son compatibles con la anulación de método.

Puede volver a declarar (nuevo), pero no puede volver a declarar y anular en el mismo tiempo (con el mismo nombre). Una opción es utilizar un método protegido a ocultar el detalle - esto permite que tanto polimorfismo y ocultando al mismo tiempo:

Las mejores soluciones sería utilizar los 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 
0

Esto es lo más cerca que podí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 se puede crear instancias de un Father<T> a menos que fuera algún otro tipo derivado.

Cuestiones relacionadas