C #: substituindo tipos de retorno

Existe maneira de replace os tipos de retorno em c #? Se sim, como, e se não, por que e o que é uma maneira recomendada de fazer isso?

Meu caso é que eu tenho uma interface com uma class base abstrata e descendentes disso. Eu gostaria de fazer isso (ok, não realmente, mas como exemplo!):

public interface Animal { Poo Excrement { get; } } public class AnimalBase { public virtual Poo Excrement { get { return new Poo(); } } } public class Dog { // No override, just return normal poo like normal animal } public class Cat { public override RadioactivePoo Excrement { get { return new RadioActivePoo(); } } } 

RadioactivePoo naturalmente herda de Poo .

Minha razão para querer isso é para que aqueles que usam objects Cat possam usar a propriedade Excrement sem ter que converter o Poo em RadioactivePoo enquanto, por exemplo, o Cat ainda pode fazer parte de uma lista de Animal onde os usuários podem não estar necessariamente conscientes ou preocupados cocô radioativo. Espero que tenha feito sentido …

Tanto quanto eu posso ver o compilador não permite isso pelo menos. Então eu acho que é impossível. Mas o que você recomendaria como uma solução para isso?

Que tal uma class base genérica?

 public class Poo { } public class RadioactivePoo : Poo { } public class BaseAnimal where PooType : Poo, new() { PooType Excrement { get { return new PooType(); } } } public class Dog : BaseAnimal { } public class Cat : BaseAnimal { } 

EDIT : Uma nova solução, usando methods de extensão e uma interface de marcador …

 public class Poo { } public class RadioactivePoo : Poo { } // just a marker interface, to get the poo type public interface IPooProvider { } // Extension method to get the correct type of excrement public static class IPooProviderExtension { public static PooType StronglyTypedExcrement( this IPooProvider iPooProvider) where PooType : Poo { BaseAnimal animal = iPooProvider as BaseAnimal; if (null == animal) { throw new InvalidArgumentException("iPooProvider must be a BaseAnimal."); } return (PooType)animal.Excrement; } } public class BaseAnimal { public virtual Poo Excrement { get { return new Poo(); } } } public class Dog : BaseAnimal, IPooProvider { } public class Cat : BaseAnimal, IPooProvider { public override Poo Excrement { get { return new RadioactivePoo(); } } } class Program { static void Main(string[] args) { Dog dog = new Dog(); Poo dogPoo = dog.Excrement; Cat cat = new Cat(); RadioactivePoo catPoo = cat.StronglyTypedExcrement(); } } 

Dessa maneira, o Dog e o Cat herdam o Animal (como comentado nos comentários, minha primeira solução não preservou a inheritance).
É necessário marcar explicitamente as classs com a interface do marcador, o que é doloroso, mas talvez isso possa lhe dar algumas idéias …

SEGUNDA EDITAÇÃO @Svish: Eu modifiquei o código para mostrar explicitamente que o método de extensão não está impondo de forma alguma o fato de que o iPooProvider herda de BaseAnimal . O que você quer dizer com “ainda mais fortemente tipado”?

Isso é chamado de covariância de tipo de retorno e não é suportado em C # ou .NET em geral, apesar dos desejos de algumas pessoas.

O que eu faria seria manter a mesma assinatura, mas adicionar uma cláusula ENSURE adicional à class derivada, na qual asseguro que esta retorne um RadioActivePoo . Então, em resumo, eu faria via design por contrato o que eu não posso fazer via syntax.

Outros preferem fingir em vez disso. Tudo bem, eu acho, mas tenho a tendência de economizar linhas de código de “infra-estrutura”. Se a semântica do código for clara o suficiente, eu estou feliz e o design por contrato me permite alcançar isso, embora não seja um mecanismo de tempo de compilation.

O mesmo para os genéricos, que outras respostas sugerem. Eu os usaria por uma razão melhor do que apenas devolver poças radioativas – mas isso é só comigo.

Eu sei que existem muitas soluções para esse problema, mas acho que descobri uma que corrige os problemas que tive com as soluções existentes.

Eu não estava feliz com as soluções existentes pelos seguintes motivos:

  • A primeira solução de Paolo Tedesco: Cat and Dog não tem uma class base comum.
  • A segunda solução de Paolo Tedesco: é um pouco complicado e difícil de ler.
  • Solução de Daniel Daranas: Isso funciona, mas trepidaria seu código com um monte de conversão desnecessária e instruções Debug.Assert ().
  • Soluções do hjb417: Esta solução não permite que você mantenha sua lógica em uma class base. A lógica é bastante trivial neste exemplo (chamando um construtor), mas em um exemplo do mundo real não seria.

Minha solução

Essa solução deve superar todos os problemas mencionados acima usando genéricos e ocultação de methods.

 public class Poo { } public class RadioactivePoo : Poo { } interface IAnimal { Poo Excrement { get; } } public class BaseAnimal : IAnimal where PooType : Poo, new() { Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } } public PooType Excrement { get { return new PooType(); } } } public class Dog : BaseAnimal { } public class Cat : BaseAnimal { } 

Com esta solução, não é necessário replace nada no Dog OR Cat! Quão legal é isso? Aqui está um exemplo de uso:

 Cat bruce = new Cat(); IAnimal bruceAsAnimal = bruce as IAnimal; Console.WriteLine(bruce.Excrement.ToString()); Console.WriteLine(bruceAsAnimal.Excrement.ToString()); 

Isso resultará em: “RadioactivePoo” duas vezes, o que mostra que o polymorphism não foi quebrado.

Leitura Adicional

  • Implementação Explícita de Interface
  • novo modificador . Eu não usei essa solução simplificada, mas você pode precisar dela em uma solução mais complicada. Por exemplo, se você quisesse criar uma interface para BaseAnimal, você precisaria usá-la em sua declaração de “PooType Excrement”.
  • Modificador genérico (covariância) . Novamente, eu não usei essa solução, mas se você quisesse fazer algo como retornar MyType de IAnimal e retornar MyType de BaseAnimal, você precisaria usá-lo para poder converter entre os dois.

Há também essa opção (implementação de interface explícita)

 public class Cat:Animal { Poo Animal.Excrement { get { return Excrement; } } public RadioactivePoo Excrement { get { return new RadioactivePoo(); } } } 

Você perde a capacidade de usar a class base para implementar o Cat, mas no lado positivo, você mantém o polymorphism entre Cat e Dog.

Mas duvido que a complexidade adicional valha a pena.

Por que não definir um método virtual protegido que cria o ‘Excremento’ e manter a propriedade pública que retorna o ‘Excremento’ não virtual. Então, as classs derivadas podem replace o tipo de retorno da class base.

No exemplo a seguir, eu faço ‘Excremento’ não virtual, mas forneço a propriedade ExcrementImpl para permitir que classs derivadas forneçam o ‘Poo’ apropriado. Os tipos derivados podem então replace o tipo de retorno de “Excremento”, ocultando a implementação da class base.

Ex:

 namepace ConsoleApplication8 { public class Poo { } public class RadioactivePoo : Poo { } public interface Animal { Poo Excrement { get; } } public class AnimalBase { public Poo Excrement { get { return ExcrementImpl; } } protected virtual Poo ExcrementImpl { get { return new Poo(); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public class Cat : AnimalBase { protected override Poo ExcrementImpl { get { return new RadioactivePoo(); } } public new RadioactivePoo Excrement { get { return (RadioactivePoo)ExcrementImpl; } } } } 

Corrija-me se estiver errado, mas não é o ponto inteiro do pollymorphism ser capaz de retornar RadioActivePoo se ele herdar de Poo, o contrato seria o mesmo que a class abstrata, mas apenas retornaria RadioActivePoo ()

Tente isto:

 namespace ClassLibrary1 { public interface Animal { Poo Excrement { get; } } public class Poo { } public class RadioactivePoo { } public class AnimalBase { public virtual T Excrement { get { return default(T); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public class Cat : AnimalBase { public override RadioactivePoo Excrement { get { return new RadioactivePoo(); } } } } 

Acho que encontrei uma maneira que não depende de methods genéricos ou de extensão, mas sim da ocultação de methods. Ele pode quebrar o polymorphism, portanto, seja especialmente cuidadoso se você herdar ainda mais o Cat.

Espero que este post ainda possa ajudar alguém, apesar de estar 8 meses atrasado.

 public interface Animal { Poo Excrement { get; } } public class Poo { } public class RadioActivePoo : Poo { } public class AnimalBase : Animal { public virtual Poo Excrement { get { return new Poo(); } } } public class Dog : AnimalBase { // No override, just return normal poo like normal animal } public class CatBase : AnimalBase { public override Poo Excrement { get { return new RadioActivePoo(); } } } public class Cat : CatBase { public new RadioActivePoo Excrement { get { return (RadioActivePoo) base.Excrement; } } } 

PARA SUA INFORMAÇÃO. Isso é implementado com bastante facilidade no Scala.

 trait Path trait Resource { def copyTo(p: Path): Resource } class File extends Resource { override def copyTo(p: Path): File = new File override def toString = "File" } class Directory extends Resource { override def copyTo(p: Path): Directory = new Directory override def toString = "Directory" } val test: Resource = new Directory() test.copyTo(null) 

Aqui está um exemplo ao vivo com o qual você pode brincar: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

Pode ajudar se o RadioactivePoo for derivado do poo e depois usar genéricos.

Eu acredito que sua resposta é chamada de covariância.

 class Program { public class Poo { public virtual string Name { get{ return "Poo"; } } } public class RadioactivePoo : Poo { public override string Name { get { return "RadioactivePoo"; } } public string DecayPeriod { get { return "Long time"; } } } public interface IAnimal where T : Poo { T Excrement { get; } } public class Animal:IAnimal where T : Poo { public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } private T _excrement; } public class Dog : Animal{} public class Cat : Animal{} static void Main(string[] args) { var dog = new Dog(); var cat = new Cat(); IAnimal animal1 = dog; IAnimal animal2 = cat; Poo dogPoo = dog.Excrement; //RadioactivePoo dogPoo2 = dog.Excrement; // Error, dog poo is not RadioactivePoo. Poo catPoo = cat.Excrement; RadioactivePoo catPoo2 = cat.Excrement; Poo animal1Poo = animal1.Excrement; Poo animal2Poo = animal2.Excrement; //RadioactivePoo animal2RadioactivePoo = animal2.Excrement; // Error, IAnimal reference do not know better. Console.WriteLine("Dog poo name: {0}",dogPoo.Name); Console.WriteLine("Cat poo name: {0}, decay period: {1}" ,catPoo.Name, catPoo2.DecayPeriod); Console.WriteLine("Press any key"); var key = Console.ReadKey(); } } 

Você poderia apenas usar um retorno de uma interface. No seu caso, IPoo.

Isso é preferível ao uso de um tipo genérico, no seu caso, porque você está usando uma class base de comentário.

Bem, é realmente possível retornar um tipo concreto que varia do tipo de retorno herdado (mesmo para methods estáticos), graças à dynamic :

 public abstract class DynamicBaseClass { public static dynamic Get (int id) { throw new NotImplementedException(); } } public abstract class BaseClass : DynamicBaseClass { public static new BaseClass Get (int id) { return new BaseClass(id); } } public abstract class DefinitiveClass : BaseClass { public static new DefinitiveClass Get (int id) { return new DefinitiveClass(id); } public class Test { public static void Main() { var testBase = BaseClass.Get(5); // No cast required, IntelliSense will even tell you // that var is of type DefinitiveClass var testDefinitive = DefinitiveClass.Get(10); } } 

Implementei isso em um wrapper de API que escrevi para minha empresa. Se você planeja desenvolver uma API, isso tem potencial para melhorar a usabilidade e a experiência de desenvolvimento em alguns casos de uso. No entanto, o uso da dynamic tem um impacto no desempenho, por isso tente evitá-lo.