Question

Existe-t-il un moyen de remplacer les types de retour en C #? Si oui comment et sinon pourquoi et quelle est la méthode recommandée?

Mon cas est que j’ai une interface avec une classe de base abstraite et ses descendants. J'aimerais faire ceci (ok pas vraiment, mais à titre d'exemple!):

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 bien sûr hérite de Poo.

Ce que je veux, c'est que ceux qui utilisent des Cat objets puissent utiliser la propriété Excrement sans avoir à convertir le Animal en <=>, tandis que, par exemple, le <=> pourrait toujours faire partie d'un <=> liste où les utilisateurs peuvent ne pas nécessairement être conscients de ou se soucier de leur caca radioactif. J'espère que cela a du sens ...

Autant que je sache, le compilateur ne le permet pas du moins. Donc je suppose que c'est impossible. Mais que recommanderiez-vous comme solution à cela?

Était-ce utile?

La solution

Qu'en est-il d'une classe de base générique?

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

MODIFIER : une nouvelle solution utilisant des méthodes d'extension et une interface de marqueur ...

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

De cette façon, Dog et Cat héritent tous les deux d'Animal (comme indiqué dans les commentaires, ma première solution n'a pas préservé l'héritage).
Il est nécessaire de marquer explicitement les classes avec l’interface marqueur, ce qui est pénible, mais peut-être que cela pourrait vous donner quelques idées ...

SECOND EDIT @Svish: j'ai modifié le code pour indiquer explicitement que la méthode d'extension n'impose aucunement le fait que iPooProvider hérite de BaseAnimal. Que voulez-vous dire par & "Encore plus fortement typé &";

?

Autres conseils

Ceci est appelé covariance du type de retour et n'est pas pris en charge en C # ou .NET en général, bien que certaines personnes souhaite .

Ce que je ferais, c’est de garder la même signature mais d’ajouter une clause ENSURE supplémentaire à la classe dérivée dans laquelle je garantis que celle-ci retourne un RadioActivePoo. Donc, en bref, je ferais par contrat, ce que je ne peux pas faire par syntaxe.

D'autres préfèrent la falsifier à la place. C'est bon, je suppose, mais j'ai tendance à économiser & "Infrastructure &"; lignes de code. Si la sémantique du code est suffisamment claire, je suis contente et la conception par contrat me permet de le réaliser, bien que ce ne soit pas un mécanisme de compilation.

Même chose pour les génériques, ce que propose . Je les utiliserais pour une meilleure raison que le simple retour du caca radioactif - mais ce n'est que moi.

Je sais qu'il existe déjà de nombreuses solutions à ce problème, mais je pense en avoir trouvé une qui résout les problèmes que j'avais avec les solutions existantes.

Certaines des solutions existantes ne me convenaient pas pour les raisons suivantes:

  • Première solution de Paolo Tedesco: Cat et Dog n'ont pas de classe de base commune.
  • Deuxième solution de Paolo Tedesco: c'est un peu compliqué et difficile à lire.
  • La solution de Daniel Daranas: cela fonctionne, mais votre code serait encombré de nombreuses transformations inutiles et d'instructions Debug.Assert ().
  • Les solutions de hjb417: Cette solution ne vous permet pas de conserver votre logique dans une classe de base. La logique est assez triviale dans cet exemple (appeler un constructeur), mais dans un exemple réel, ce ne serait pas le cas.

Ma solution

Cette solution devrait résoudre tous les problèmes que j'ai mentionnés ci-dessus en utilisant à la fois des génériques et le masquage de méthode.

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

Avec cette solution, vous ne devez rien substituer à Dog OR Cat! Voici quelques exemples d'utilisation:

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

Ceci produira: " RadioactivePoo " deux fois, ce qui montre que le polymorphisme n'a pas été rompu.

Autres lectures

  • Implémentation d'interface explicite
  • nouvelle modification Je ne l'ai pas utilisé dans cette solution simplifiée, mais vous en aurez peut-être besoin dans une solution plus complexe. Par exemple, si vous souhaitez créer une interface pour BaseAnimal, vous devez l’utiliser dans votre décleration de & "PooType Excrement &";
  • .
  • le modificateur générique (Covariance) . Encore une fois, je ne l’utilisais pas dans cette solution, mais si vous vouliez faire quelque chose comme retourner MyType<Poo> depuis IAnimal et MyType<PooType> depuis BaseAnimal, il vous faudrait l’utiliser pour pouvoir attribuer un casting entre les deux.

Il y a aussi cette option (implémentation d'interface explicite)

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

Vous perdez la possibilité d'utiliser la classe de base pour implémenter Cat, mais du côté plus, vous conservez le polymorphisme entre Cat et Dog.

Mais je doute que la complexité ajoutée en vaille la peine.

Pourquoi ne pas définir une méthode virtuelle protégée qui crée le 'Excrement' et conserver la propriété publique qui renvoie le 'Excrement' non virtuel. Les classes dérivées peuvent alors remplacer le type de retour de la classe de base.

Dans l'exemple suivant, je mets 'Excrement' non virtuel, mais je fournis la propriété ExcrementImpl pour permettre aux classes dérivées de fournir le 'Poo' approprié. Les types dérivés peuvent ensuite remplacer le type de retour 'Excrement' en masquant l'implémentation de la classe de 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; } }
}
}

Corrigez-moi si je me trompe, mais n’est pas l’intérêt du pollymorphisme de pouvoir renvoyer RadioActivePoo s’il hérite de Poo, le contrat serait identique à la classe abstraite mais renverrait simplement RadioActivePoo ()

Essayez ceci:

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

Je pense avoir trouvé un moyen qui ne dépend pas des méthodes génériques ou d'extension, mais plutôt du masquage de méthode. Cependant, il peut rompre le polymorphisme. Soyez donc particulièrement prudent si vous héritez davantage de Cat.

J'espère que ce message pourra toujours aider quelqu'un malgré le retard de 8 mois.

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

Cela pourrait aider si RadioactivePoo est dérivé du caca et utilise ensuite des génériques.

FYI. Ceci s’applique assez facilement en 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)

Voici un exemple concret avec lequel vous pouvez jouer: http://www.scalakata.com/50d0d6e7e7e4b0a825d655e832< >

Je crois que votre réponse s'appelle covariance.

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

Vous pouvez simplement utiliser un retour d’interface. Dans ton cas, IPoo.

Ceci est préférable à l’utilisation d’un type générique, dans votre cas, car vous utilisez une classe de base de commentaires.

Eh bien, il est effectivement possible de renvoyer un type concret qui varie par rapport au type de retour hérité (même pour les méthodes statiques), grâce à 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);
    }
}

Je l'ai implémenté dans un wrapper d'API que j'ai écrit pour mon entreprise. Si vous envisagez de développer une API, cela peut potentiellement améliorer la convivialité et l'expérience de développement dans certains cas d'utilisation. Néanmoins, l'utilisation de <=> a un impact sur les performances, essayez donc de l'éviter.

scroll top