¿C # admite la covarianza de tipo de retorno?

84

Estoy trabajando con el marco .NET y realmente quiero poder crear un tipo de página personalizado que utilice todo mi sitio web. El problema surge cuando intento acceder a la página desde un control. Quiero poder devolver mi tipo específico de página en lugar de la página predeterminada. ¿Hay alguna forma de hacer esto?

public class MyPage : Page
{
    // My own logic
}

public class MyControl : Control
{
    public MyPage Page { get; set; }
}
Kyle Sletten
fuente
posible duplicado de tipos de retorno covariantes
nawfal

Respuestas:

170

ACTUALIZACIÓN: Esta respuesta se escribió en 2011. Después de dos décadas de personas que proponen la covarianza de tipo de retorno para C #, parece que finalmente se implementará; Estoy bastante sorprendido. Consulte la parte inferior de https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ para ver el anuncio; Estoy seguro de que seguirán los detalles.


Parece que lo que desea es una covarianza de tipo de retorno. C # no admite la covarianza de tipo de retorno.

La covarianza del tipo de retorno es donde anula un método de clase base que devuelve un tipo menos específico con uno que devuelve un tipo más específico:

abstract class Enclosure
{
    public abstract Animal Contents();
}
class Aquarium : Enclosure
{
    public override Fish Contents() { ... }
}

Esto es seguro porque los consumidores de Contenidos vía Enclosure esperan un Animal, y Aquarium promete no solo cumplir con ese requisito, sino además, hacer una promesa más estricta: que el animal es siempre un pez.

Este tipo de covarianza no se admite en C # y es poco probable que lo sea alguna vez. No es compatible con CLR. (Es compatible con C ++ y con la implementación de C ++ / CLI en CLR; lo hace generando métodos de ayuda mágicos del tipo que sugiero a continuación).

(Algunos lenguajes también admiten contravarianza de tipo de parámetro formal, que puede anular un método que toma un pez con un método que toma un animal. Una vez más, el contrato se cumple; la clase base requiere que se manipule cualquier pez y el derivado class promete no solo manejar peces, sino cualquier animal. De manera similar, C # y CLR no admiten la contravarianza de tipo de parámetro formal.

La forma en que puede evitar esta limitación es hacer algo como:

abstract class Enclosure
{
    protected abstract Animal GetContents();
    public Animal Contents() { return this.GetContents(); }
}
class Aquarium : Enclosure
{
    protected override Animal GetContents() { return this.Contents(); }
    public new Fish Contents() { ... }
}

Ahora obtiene los beneficios de anular un método virtual y una escritura más fuerte cuando usa algo del tipo Acuario en tiempo de compilación.

Eric Lippert
fuente
3
¡Perfecto! ¡Me olvidé de esconderme!
Kyle Sletten
3
buena respuesta como siempre. Nos perdimos esas respuestas por un tiempo.
Dhananjay
4
¿Existe una justificación para no admitir la covarianza de retorno y la contravarianza de parámetros disponibles para leer en cualquier lugar?
Porges
26
@EricLippert Siendo un groupie de C #, siempre tengo curiosidad por el "backstage", así que solo tengo que preguntar: ¿Se consideró alguna vez la covarianza de tipo de retorno para C # y se consideró que tenía un ROI demasiado bajo? Entiendo las limitaciones de tiempo y errores, pero a partir de su declaración de "es poco probable que alguna vez sea compatible", parece que el equipo de diseño preferiría NO tener esto en el idioma. En el otro lado de la valla, Java introdujo esta característica del lenguaje en Java 5, así que me pregunto cuál es la diferencia en los principios generales que gobiernan el proceso de diseño del lenguaje.
Cristian Diaconescu
7
@Cyral: Correcto; está en la lista del grupo de "interés moderado", que es donde ha estado durante mucho tiempo. Podría suceder, pero soy bastante escéptico.
Eric Lippert
8

Con las interfaces lo resolví implementando explícitamente la interfaz:

public interface IFoo {
  IBar Bar { get; }
}
public class Foo : IFoo {
  Bar Bar { get; set; }
  IBar IFoo.Bar => Bar;
}
ChetPrickles
fuente
2

Colocar esto en el objeto MyControl funcionaría:

 public new MyPage Page {get return (MyPage)Page; set;}'

No puede anular la propiedad porque devuelve un tipo diferente ... pero puede redefinirlo.

No necesita covarianza en este ejemplo, ya que es relativamente simple. Todo lo que estás haciendo es heredar el objeto base Pagede MyPage. Cualquiera Controlque desee devolver en MyPagelugar de Pagedebe redefinir la Pagepropiedad delControl

Zhais
fuente
1

Sí, admite la covarianza, pero depende de lo que esté tratando de lograr exactamente.

También tiendo a usar mucho los genéricos para las cosas, lo que significa que cuando haces algo como:

class X<T> {
    T doSomething() {
    }

}

class Y : X<Y> {
    Y doSomethingElse() {
    }
}

var Y y = new Y();
y = y.doSomething().doSomethingElse();

Y no "perder" tus tipos.

Cade Roux
fuente
1

Esta es una función para el próximo C # 9.0 (.Net 5) de la cual puede descargar una versión preliminar ahora.

El siguiente código construye ahora con éxito (sin dar: error CS0508: 'Tiger.GetFood()': return type must be 'Food' to match overridden member 'Animal.GetFood()')

class Food { }
class Meat : Food { }

abstract class Animal {
    public abstract Food GetFood();
}

class Tiger : Animal {
    public override Meat GetFood() => default;
}

class Program {
    static void Main() => new Tiger();
}
Neurona
fuente
0

No lo he probado, pero ¿no funciona?

YourPageType myPage = (YourPageType)yourControl.Page;
AGoodDisplayName
fuente
1
Sí lo hace. Solo quiero tratar de evitar el casting por todos lados.
Kyle Sletten
0

Si. Hay varias formas de hacer esto, y esta es solo una opción:

Puede hacer que su página implemente alguna interfaz personalizada que exponga un método llamado "GetContext" o algo, y devuelva su información específica. Entonces su control puede simplemente solicitar la página y transmitir:

var myContextPage = this.Page as IMyContextGetter;

if(myContextPage != null)
   var myContext = myContextPage.GetContext();

Entonces puedes usar ese contexto como quieras.

Tejs
fuente
0

Puede acceder a su página desde cualquier control subiendo por el árbol principal. Es decir

myParent = this;

while(myParent.parent != null)
  myParent = myParent.parent;

* No compiló ni probó.

O obtenga la página principal en el contexto actual (depende de su versión).


Entonces lo que me gusta hacer es esto: creo una interfaz con las funciones que quiero usar en el control (por ejemplo, IHostingPage)

Luego lanzo la página principal 'IHostingPage host = (IHostingPage) Parent;' y estoy listo para llamar a la función en la página que necesito desde mi control.

Hogan
fuente
0

Lo haré de esta manera:

class R {
    public int A { get; set; }
}

class R1: R {
    public int B { get; set; }
}

class A
{        
    public R X { get; set; }
}

class B : A 
{
    private R1 _x;
    public new R1 X { get => _x; set { ((A)this).X = value; _x = value; } }
}
Indomable
fuente