Pergunta

Existe maneira de tipos de substituição de retorno em C #? Se sim, como, e se não o porquê eo que é um meio recomendado de fazê-lo?

O meu caso é que eu tenho uma interface com uma classe base abstrata e descendentes daquele. Eu gostaria de fazer isso (ok, não realmente, mas como um 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 de herda curso de Poo.

A minha razão para querer isso é para que aqueles que usam objetos Cat poderia usar a propriedade Excrement sem ter que lançar o Poo em RadioactivePoo enquanto por exemplo, o Cat ainda poderia ser parte de uma lista Animal onde os usuários podem não ser necessariamente consciente ou preocupam com a sua poo radioativo. Esperança de que fazia sentido ...

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

Foi útil?

Solução

Que tal uma classe base genérico?

public class Poo { }
public class RadioactivePoo : Poo { }

public class BaseAnimal<PooType> 
    where PooType : Poo, new() {
    PooType Excrement {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }

Editar : A nova solução, usando métodos de extensão e um marcador de interface ...

public class Poo { }
public class RadioactivePoo : Poo { }

// just a marker interface, to get the poo type
public interface IPooProvider<PooType> { }

// Extension method to get the correct type of excrement
public static class IPooProviderExtension {
    public static PooType StronglyTypedExcrement<PooType>(
        this IPooProvider<PooType> 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<Poo> { }

public class Cat : BaseAnimal, IPooProvider<RadioactivePoo> {
    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();
    }
}

Desta forma, cão e gato ambos herdam Animal (como observou nos comentários, a minha primeira solução não preservar a herança).
É necessário marcar explicitamente as classes com a interface de marcador, que é doloroso, mas talvez isso poderia lhe dar algumas idéias ...

SEGUNDO EDIT @Svish: Eu modifiquei o código para mostrar explitly que o método de extensão não está a aplicar de forma alguma o fato de que herda iPooProvider de BaseAnimal. O que você quer dizer com "ainda mais fortemente tipado"?

Outras dicas

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

O que gostaria de fazer é manter a mesma assinatura, mas acrescentar uma cláusula ENSURE adicional para a classe derivada em que eu garantir que esta retorna um RadioActivePoo. Então, em suma, eu faria via programação por contrato que eu não posso fazer através de sintaxe.

Outros preferem falso -lo em seu lugar. É ok, eu acho, mas eu tendem a economizar linhas "infra-estrutura" de código. Se a semântica do código são suficientes claro, estou feliz, e design por contrato me permite conseguir isso, embora não seja um mecanismo de tempo de compilação.

O mesmo para os genéricos, que outras respostas sugerir. Eu iria usá-los por uma razão melhor do que apenas retornando poo radioativa -. Mas isso é apenas me

Eu sei que há um monte de soluções para este problema já, mas eu acho que eu vim acima com um que corrige os problemas que tive com as soluções existentes.

eu não estava feliz com a algumas das soluções existentes para os seguintes motivos:

  • primeira solução de Paolo Tedesco:. Cat and Dog não tem uma classe base comum
  • segunda solução de Paolo Tedesco:. É um pouco complicado e difícil de ler
  • solução de Daniel Daranas:. Isso funciona, mas seria atravancam seu código com um monte de fundição desnecessário e Debug.Assert () declarações
  • soluções de hjb417: Esta solução não deixá-lo manter sua lógica em uma classe base. A lógica é bastante trivial neste exemplo (chamando um construtor), mas em um exemplo do mundo real não seria.

Minha solução

Esta solução deve superar todos os problemas que eu mencionei acima, utilizando ambos os genéricos e método de esconder.

public class Poo { }
public class RadioactivePoo : Poo { }

interface IAnimal
{
    Poo Excrement { get; }
}

public class BaseAnimal<PooType> : IAnimal
    where PooType : Poo, new()
{
    Poo IAnimal.Excrement { get { return (Poo)this.Excrement; } }

    public PooType Excrement
    {
        get { return new PooType(); }
    }
}

public class Dog : BaseAnimal<Poo> { }
public class Cat : BaseAnimal<RadioactivePoo> { }

Com esta solução, você não precisa para substituir qualquer coisa no cão ou gato! Aqui estão algumas uso de amostra:

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

A saída será: "RadioactivePoo" duas vezes o que mostra que o polimorfismo não foi quebrado

Leitura adicional

  • interface explícita Implementação
  • novo modificador . Eu não usá-lo nesta solução simplificada, mas você pode precisar dele em uma solução mais complicada. Por exemplo, se você quisesse criar uma interface para BaseAnimal então você precisa usá-lo em sua decleration de "PooType excremento".
  • fora modificador genérico (Covariância) . Mais uma vez eu não usá-lo nesta solução, mas se você queria fazer algo como MyType<Poo> retorno de IAnimal e MyType<PooType> retorno de BaseAnimal então você precisa usá-lo para ser capaz de elenco entre os dois.

Há também essa opção (interface de implementação 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 classe base para implementar o gato, mas no lado positivo, o, você manter o polimorfismo entre gato e cão.

Mas eu duvido que a complexidade adicional vale a pena.

Por que não definir um método virtual protegido que cria a 'Merda' e manter a propriedade pública que retorna o 'excremento' não virtual. Em seguida, as classes derivadas podem substituir o tipo de retorno da classe base.

No exemplo a seguir, faço não-virtuais, mas fornecer os ExcrementImpl propriedade 'excremento' para permitir que as classes derivadas para fornecer o bom 'Poo'. tipos derivados podem substituir o tipo de retorno de 'Merda', escondendo a implementação da classe base.

E.x:.

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; } }
}
}

me corrigir se estou errado, mas is not todo o ponto de pollymorphism para ser capaz de voltar RadioActivePoo se herda de Poo, o contrato seria o mesmo que a classe abstrata, mas apenas retornar RadioActivePoo ()

Tente isto:

namespace ClassLibrary1
{
    public interface Animal
    {   
        Poo Excrement { get; }
    }

    public class Poo
    {
    }

    public class RadioactivePoo
    {
    }

    public class AnimalBase<T>
    {   
        public virtual T Excrement
        { 
            get { return default(T); } 
        }
    }


    public class Dog : AnimalBase<Poo>
    {  
        // No override, just return normal poo like normal animal
    }

    public class Cat : AnimalBase<RadioactivePoo>
    {  
        public override RadioactivePoo Excrement 
        {
            get { return new RadioactivePoo(); } 
        }
    }
}

Eu acho que eu encontrei uma maneira que não depende de genéricos ou métodos de extensão, mas sim esconder método. Pode quebrar o polimorfismo, no entanto, assim que ter um cuidado especial se você ainda herdar de Cat.

Espero que este post poderia ainda ajudar alguém, apesar de ser 8 meses de atraso.

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; } }
}

Pode ajudar se RadioactivePoo é derivado de poo e, em seguida, usar os genéricos.

FYI. Isso é implementado com bastante facilidade em 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 vivo você pode jogar com: http://www.scalakata.com/50d0d6e7e4b0a825d655e832

Eu acredito que a sua resposta é chamado 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<out T> where T : Poo
    {
        T Excrement { get; }
    }

    public class Animal<T>:IAnimal<T> where T : Poo 
    {
        public T Excrement { get { return _excrement ?? (_excrement = (T) Activator.CreateInstance(typeof (T), new object[] {})); } } 
        private T _excrement;
    }

    public class Dog : Animal<Poo>{}
    public class Cat : Animal<RadioactivePoo>{}

    static void Main(string[] args)
    {
        var dog = new Dog();
        var cat = new Cat();

        IAnimal<Poo> animal1 = dog;
        IAnimal<Poo> 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<Poo> 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ê pode simplesmente usar um retorno de uma Interface. No seu caso, ipoo.

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

Bem, é realmente possível para retornar um tipo concreto que varia de O tipo de retorno herdado (mesmo para métodos estáticos), graças a 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);
    }
}

Eu implementei isso em uma API invólucro eu escrevi para a minha empresa. Se você pretende desenvolver uma API, isso tem potencial para melhorar a usabilidade e dev experiência em alguns casos de uso. No entanto, o uso de dynamic tem um impacto para o desempenho, de forma a tentar evitá-lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top