C # suporta covariância de tipo de retorno?

84

Estou trabalhando com o .NET framework e realmente quero ser capaz de fazer um tipo de página personalizado que todo o meu site use. O problema surge quando tento acessar a página de um controle. Desejo poder retornar meu tipo específico de página em vez da página padrão. Há alguma maneira de fazer isso?

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

public class MyControl : Control
{
    public MyPage Page { get; set; }
}
Kyle Sletten
fonte

Respostas:

169

ATUALIZAÇÃO: esta resposta foi escrita em 2011. Após duas décadas de pessoas propondo covariância de tipo de retorno para C #, parece que finalmente será implementado; Estou bastante surpreso. Consulte o final de https://devblogs.microsoft.com/dotnet/welcome-to-c-9-0/ para obter o anúncio; Tenho certeza de que os detalhes virão.


Parece que o que você quer é a covariância do tipo de retorno. C # não oferece suporte à covariância de tipo de retorno.

A covariância do tipo de retorno é onde você substitui um método de classe base que retorna um tipo menos específico por outro que retorna um tipo mais específico:

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

Isso é seguro porque os consumidores de Conteúdo via Enclosure esperam um Animal, e o Aquarium promete não apenas cumprir esse requisito, mas, além disso, fazer uma promessa mais rígida: que o animal é sempre um peixe.

Esse tipo de covariância não é compatível com C # e provavelmente nunca será compatível. Não é compatível com o CLR. (É suportado por C ++ e pela implementação C ++ / CLI no CLR; ele o faz gerando métodos auxiliares mágicos do tipo que sugiro abaixo.)

(Algumas linguagens também suportam contravariância de tipo de parâmetro formal - que você pode substituir um método que pega um Peixe por um método que pega um Animal. Novamente, o contrato é cumprido; a classe base requer que qualquer Peixe seja manipulado, e o derivado classe promete não apenas lidar com peixes, mas qualquer animal. Da mesma forma, C # e o CLR não suportam contravariância de tipo de parâmetro formal.)

A maneira de contornar essa limitação é fazer 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() { ... }
}

Agora você obtém os benefícios de substituir um método virtual e obter uma digitação mais forte ao usar algo do tipo Aquarium em tempo de compilação.

Eric Lippert
fonte
3
Perfeito! Eu esqueci de me esconder!
Kyle Sletten
3
boa resposta como sempre. Estávamos perdendo essas respostas por um tempo.
Dhananjay de
4
Existe uma justificativa para não apoiar a covariância de retorno e a contravariância de parâmetro disponíveis para leitura em qualquer lugar?
porges
26
@EricLippert Sendo uma groupie de C #, estou sempre curioso sobre os "bastidores", então só tenho que perguntar: A covariância do tipo de retorno foi considerada para C # e considerada como tendo um ROI muito baixo? Eu entendo as restrições de tempo e bugdet, mas de sua declaração "improvável que algum dia seja suportado", parece que a equipe de design preferiria NÃO ter isso na linguagem. Do outro lado da cerca, Java introduziu esse recurso de linguagem em Java 5, então eu me pergunto qual é a diferença nos princípios maiores e abrangentes que governam o processo de design de linguagem.
Cristian Diaconescu
7
@Cyral: correto; está na lista do balde de "interesse moderado" - que é onde está há muito tempo! Pode acontecer, mas sou bastante cético.
Eric Lippert
8

Com interfaces, resolvi isso implementando explicitamente a interface:

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

Colocá-lo no objeto MyControl funcionaria:

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

Você não pode substituir a propriedade porque ela retorna um tipo diferente ... mas você pode redefini-la.

Você não precisa de covariância neste exemplo, pois é relativamente simples. Tudo o que você está fazendo é herdar o objeto base Pagede MyPage. Qualquer um Controlque você deseja retornar, em MyPagevez de, Pageprecisa redefinir a Pagepropriedade doControl

Zhais
fonte
1

Sim, ele oferece suporte à covariância, mas depende do que você está tentando alcançar exatamente.

Eu também costumo usar muito os genéricos para as coisas, o que significa que quando você faz algo como:

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

}

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

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

E não "perder" seus tipos.

Cade Roux
fonte
1

Este é um recurso para o próximo C # 9.0 (.Net 5), do qual você pode baixar uma versão de amostra agora.

O código a seguir agora é compilado com sucesso (sem fornecer 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();
}
Neurônio
fonte
0

Eu não tentei, mas isso não funciona?

YourPageType myPage = (YourPageType)yourControl.Page;
AGoodDisplayName
fonte
1
Sim ele faz. Eu só quero tentar evitar lançar para todos os lados.
Kyle Sletten
0

Sim. Existem várias maneiras de fazer isso, e esta é apenas uma opção:

Você pode fazer sua página implementar alguma interface personalizada que exponha um método chamado "GetContext" ou algo assim, e retorne suas informações específicas. Em seguida, seu controle pode simplesmente solicitar a página e o elenco:

var myContextPage = this.Page as IMyContextGetter;

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

Então você pode usar esse contexto como desejar.

Tejs
fonte
0

Você pode acessar sua página a partir de qualquer controle subindo pela árvore pai. Isso é

myParent = this;

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

* Não compilou ou testou.

Ou obtenha a página principal no contexto atual (depende da sua versão).


Então o que eu gosto de fazer é o seguinte: Eu crio uma interface com as funções que desejo usar no controle (por exemplo, IHostingPage)

Então eu transmito a página pai 'IHostingPage host = (IHostingPage) Parent;' e estou pronto para chamar a função na página que preciso do meu controle.

Hogan
fonte
0

Vou fazer desta forma:

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; } }
}
Indomável
fonte