web-dev-qa-db-fra.com

Héritage multiple en C #

Comme l'héritage multiple est mauvais (la source est plus compliquée), C # ne fournit pas directement un tel motif. Mais parfois, il serait utile d’avoir cette capacité.

Par exemple, je suis capable d'implémenter le modèle d'héritage multiple manquant en utilisant des interfaces et trois classes comme celle-ci:

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst 
{ 
    public void FirstMethod() { Console.WriteLine("First"); } 
}

public class Second:ISecond 
{ 
    public void SecondMethod() { Console.WriteLine("Second"); } 
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

Chaque fois que j'ajoute une méthode à l'une des interfaces, je dois aussi changer de classe FirstAndSecond.

Existe-t-il un moyen d'injecter plusieurs classes existantes dans une nouvelle classe, comme c'est possible en C++?

Peut-être existe-t-il une solution utilisant une sorte de génération de code?

Ou cela peut ressembler à ceci (syntaxe imaginaire c #):

public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

Ainsi, il ne sera pas nécessaire de mettre à jour la classe FirstAndSecond lorsque je modifierai l'une des interfaces.


MODIFIER

Peut-être serait-il préférable de considérer un exemple pratique:

Vous avez une classe existante (par exemple, un client TCP basé sur du texte basé sur ITextTcpClient) que vous utilisez déjà à différents endroits de votre projet. Vous sentez maintenant le besoin de créer un composant de votre classe pour qu'il soit facilement accessible aux développeurs de formulaires Windows.

Autant que je sache, vous avez actuellement deux moyens de le faire:

  1. Écrivez une nouvelle classe héritée des composants et implémente l'interface de la classe TextTcpClient en utilisant une instance de la classe elle-même, comme indiqué avec FirstAndSecond.

  2. Ecrivez une nouvelle classe qui hérite de TextTcpClient et implémente IComponent (je ne l’ai pas encore essayé).

Dans les deux cas, vous devez travailler par méthode et non par classe. Puisque vous savez que nous aurons besoin de toutes les méthodes de TextTcpClient et de Component, ce serait la solution la plus simple pour combiner simplement ces deux méthodes en une seule classe.

Pour éviter les conflits, cela peut être fait par la génération de code où le résultat pourrait être modifié par la suite, mais taper cela à la main est une pure douleur dans le cul.

202
Martin

Comme l'héritage multiple est mauvais (la source est plus compliquée), C # ne fournit pas directement un tel motif. Mais parfois, il serait utile d’avoir cette capacité.

C # et le .net CLR n’ont pas implémenté MI parce qu’ils n’ont pas encore conclu comment cela interagirait entre C #, VB.net et les autres langues, pas parce que "cela rendrait la source plus complexe"

MI est un concept utile. Les questions non répondues sont les suivantes: - "Que faites-vous lorsque vous avez plusieurs classes de base communes dans les différentes super-classes?"

Perl est la seule langue avec laquelle j'ai travaillé où MI fonctionne bien. Il est possible que .Net l’introduise un jour, mais pas encore, le CLR supporte déjà MI, mais comme je l’ai dit, il n’existe pas encore de construction de langage pour cela.

Jusque-là, vous êtes bloqué avec des objets proxy et plusieurs interfaces à la place :(

118
IanNorton

Pensez simplement à utiliser composition au lieu d'essayer de simuler l'héritage multiple. Vous pouvez utiliser Interfaces pour définir les classes composant la composition, par exemple: ISteerable implique une propriété de type SteeringWheel, IBrakable implique une propriété de type BrakePedal, etc.

Une fois que vous avez fait cela, vous pouvez utiliser la fonctionnalité méthodes d'extension ajoutée à C # 3.0 pour simplifier davantage les méthodes d'appel sur ces propriétés implicites, par exemple:

public interface ISteerable { SteeringWheel wheel { get; set; } }

public interface IBrakable { BrakePedal brake { get; set; } }

public class Vehicle : ISteerable, IBrakable
{
    public SteeringWheel wheel { get; set; }

    public BrakePedal brake { get; set; }

    public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}

public static class SteeringExtensions
{
    public static void SteerLeft(this ISteerable vehicle)
    {
        vehicle.wheel.SteerLeft();
    }
}

public static class BrakeExtensions
{
    public static void Stop(this IBrakable vehicle)
    {
        vehicle.brake.ApplyUntilStop();
    }
}


public class Main
{
    Vehicle myCar = new Vehicle();

    public void main()
    {
        myCar.SteerLeft();
        myCar.Stop();
    }
}
204
Chris Wenham

J'ai créé un post-compilateur C # qui permet ce genre de chose:

using NRoles;

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class RFirst : IFirst, Role {
  public void FirstMethod() { Console.WriteLine("First"); }
}

public class RSecond : ISecond, Role {
  public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

Vous pouvez exécuter le post-compilateur en tant qu'événement post-build-Visual Studio:

C:\chemin_profil\nroles-v0.1.0-bin\nutate.exe "$ (TargetPath)"

Dans la même Assemblée, vous l'utilisez comme ceci:

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

Dans une autre Assemblée, vous l'utilisez comme ceci:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
14
Jordão

Cela me plairait également - c’est ce que j’appelle personnellement un mélange, même si je réalise que c’est un terme surchargé. J'aimerais pouvoir spécifier la variable utilisée pour implémenter l'interface, avec l'option de fournir ma propre implémentation pour des méthodes spécifiques.

J'ai blogué à ce sujet plus en détail - bien que dans le contexte d'une surestimation délibérée de ce que cela pourrait signifier en termes d'héritage.

Je ne vois pas pourquoi cela ne pourrait pas être implémenté dans le compilateur C # - mais c'est un peu plus complexe en termes de langage ...

14
Jon Skeet

Vous pouvez avoir une classe de base abstraite qui implémente IFirst et ISecond, puis hériter de cette base.

5
Joel Coehoorn

MI n'est pas mauvais, tout le monde qui l'a (sérieusement) utilisé l'aimait et cela ne compliquait PAS le code! Du moins, pas plus que d’autres constructions ne peuvent compliquer le code. Un code incorrect est un code incorrect, que MI soit ou non dans l'image.

Quoi qu'il en soit, j'ai une petite solution intéressante pour l'héritage multiple que je voulais partager, c'est à; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog ou vous pouvez suivre le lien dans ma signature ... :)

2
Thomas Hansen

Oui, utiliser Interface est un problème car à chaque fois que nous ajoutons une méthode dans la classe, nous devons ajouter la signature dans l'interface. De plus, que se passe-t-il si nous avons déjà une classe avec un tas de méthodes mais pas d'interface pour cela? nous devons créer manuellement une interface pour toutes les classes dont nous voulons hériter. Et le pire, c’est que nous devons implémenter toutes les méthodes des interfaces de la classe enfant si celle-ci doit hériter de l’interface multiple.

En suivant le modèle de conception de façade, nous pouvons simuler l'héritage de plusieurs classes à l'aide de accessors. Déclarez les classes en tant que propriétés avec {get; set;} à l'intérieur de la classe qui doit hériter et toutes les propriétés et méthodes publiques proviennent de cette classe et dans le constructeur de la classe enfant, instanciez les classes parent.

Par exemple:

 namespace OOP
 {
     class Program
     {
         static void Main(string[] args)
         {
             Child somechild = new Child();
             somechild.DoHomeWork();
             somechild.CheckingAround();
             Console.ReadLine();
         }
     }

     public class Father 
     {
         public Father() { }
         public void Work()
         {
             Console.WriteLine("working...");
         }
         public void Moonlight()
         {
             Console.WriteLine("moonlighting...");
         }
     }


     public class Mother 
     {
         public Mother() { }
         public void Cook()
         {
             Console.WriteLine("cooking...");
         }
         public void Clean()
         {
             Console.WriteLine("cleaning...");
         }
     }


     public class Child 
     {
         public Father MyFather { get; set; }
         public Mother MyMother { get; set; }

         public Child()
         {
             MyFather = new Father();
             MyMother = new Mother();
         }

         public void GoToSchool()
         {
             Console.WriteLine("go to school...");
         }
         public void DoHomeWork()
         {
             Console.WriteLine("doing homework...");
         }
         public void CheckingAround()
         {
             MyFather.Work();
             MyMother.Cook();
         }
     }


 }

avec cette structure, la classe Child aura accès à toutes les méthodes et propriétés de la classe Père et Mère, en simulant un héritage multiple, en héritant d'une instance des classes parentes. Pas tout à fait pareil mais c'est pratique.

1
Yogi

Si vous pouvez vivre avec la restriction que les méthodes de IFirst et ISecond doivent uniquement interagir avec le contrat de IFirst et ISecond (comme dans votre exemple) ... vous pouvez faire ce que vous demandez avec des méthodes d'extension. En pratique, c'est rarement le cas.

public interface IFirst {}
public interface ISecond {}

public class FirstAndSecond : IFirst, ISecond
{
}

public static MultipleInheritenceExtensions
{
  public static void First(this IFirst theFirst)
  {
    Console.WriteLine("First");
  }

  public static void Second(this ISecond theSecond)
  {
    Console.WriteLine("Second");
  }
}

///

public void Test()
{
  FirstAndSecond fas = new FirstAndSecond();
  fas.First();
  fas.Second();
}

L'idée de base est donc que vous définissiez l'implémentation requise dans les interfaces ... ces éléments requis devraient prendre en charge l'implémentation flexible dans les méthodes d'extension. Chaque fois que vous devez "ajouter des méthodes à l'interface", vous ajoutez une méthode d'extension.

1
Amy B

L'héritage multiple est l'une de ces choses qui pose généralement plus de problèmes qu'elle n'en résout. En C++, cela vous donne suffisamment de corde pour vous suspendre, mais Java et C # ont choisi la voie la plus sûre qui consiste à ne pas vous donner la possibilité. Le plus gros problème est de savoir quoi faire si vous héritez de plusieurs classes ayant une méthode avec la même signature que l'héritée n'implémente pas. Quelle méthode de classe devrait-il choisir? Ou est-ce que cela ne devrait pas être compilé? Il existe généralement un autre moyen de mettre en œuvre la plupart des choses qui ne reposent pas sur l'héritage multiple.

0
tloach

Puisque la question de l'héritage multiple (MI) se pose de temps en temps, j'aimerais ajouter une approche qui résout certains problèmes liés au motif de composition.

Je me fonde sur l’approche IFirst, ISecond, First, Second, FirstAndSecond, telle qu’elle a été présentée dans la question. Je réduis l'exemple de code à IFirst, car le modèle reste le même quel que soit le nombre d'interfaces/classes de base MI.

Supposons qu'avec MI First et Second tous deux dérivent de la même classe de base BaseClass, en utilisant uniquement les éléments d'interface publique de BaseClass

Ceci peut être exprimé en ajoutant une référence de conteneur à BaseClass dans les implémentations First et Second:

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

Les choses deviennent plus compliquées, lorsque les éléments d'interface protégés de BaseClass sont référencés ou lorsque First et Second sont des classes abstraites dans MI, nécessitant que leurs sous-classes implémentent certaines parties abstraites.

class BaseClass {
  protected void DoStuff();
}

abstract class First : IFirst {
  public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
  protected abstract void DoStuff(); // base class reference in MI
  protected abstract void DoSubClassStuff(); // sub class responsibility
}

C # permet aux classes imbriquées d'accéder aux éléments protégés/privés de leurs classes qui les contiennent, ce qui permet de lier les bits abstraits de l'implémentation First.

class FirstAndSecond : BaseClass, IFirst, ISecond {
  // link interface
  private class PartFirst : First {
    private FirstAndSecond ContainerInstance;
    public PartFirst(FirstAndSecond container) {
      ContainerInstance = container;
    }
    // forwarded references to emulate access as it would be with MI
    protected override void DoStuff() { ContainerInstance.DoStuff(); }
    protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
  }
  private IFirst partFirstInstance; // composition object
  public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
  public FirstAndSecond() {
    partFirstInstance = new PartFirst(this); // composition in constructor
  }
  // same stuff for Second
  //...
  // implementation of DoSubClassStuff
  private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}

Il y a pas mal de choses en jeu, mais si l'implémentation réelle de FirstMethod et de SecondMethod est suffisamment complexe et que le nombre de méthodes privées/protégées auxquelles vous accédez est modéré, ce modèle peut aider à remédier au manque d'héritage multiple.

0
grek40

Dans ma propre implémentation, j’ai constaté que l’utilisation de classes/interfaces pour MI, bien que de "bonne forme", tendait à être une complication énorme, car vous devez configurer tout cet héritage multiple pour seulement quelques appels de fonction nécessaires, et dans mon cas, devait être fait littéralement des dizaines de fois de manière redondante.

Au lieu de cela, il était plus facile de créer simplement des "fonctions appelant des fonctions appelant des fonctions" statiques dans différentes variétés modulaires, comme une sorte de OOP remplacement. La solution sur laquelle je travaillais était le "système de sorts" pour un RPG dans lequel les effets doivent faire appel à une fonction très complexe pour donner une grande variété de sorts sans réécrire. code, un peu comme l'exemple semble l'indiquer.

La plupart des fonctions peuvent maintenant être statiques car je n'ai pas nécessairement besoin d'une instance pour la logique orthographique, alors que l'héritage de classe ne peut même pas utiliser de mots-clés abstraits virtuels ou tout en étant statiques. Les interfaces ne peuvent pas les utiliser du tout.

Le codage semble beaucoup plus rapide et plus propre de cette façon, IMO. Si vous ne faites que des fonctions et que vous n'avez pas besoin de propriétés héritées , utilisez des fonctions.

0
hydrix

Cela correspond à la réponse de Lawrence Wenham, mais selon votre cas d'utilisation, cela peut être une amélioration ou non - vous n'avez pas besoin des régleurs.

public interface IPerson {
  int GetAge();
  string GetName();
}

public interface IGetPerson {
  IPerson GetPerson();
}

public static class IGetPersonAdditions {
  public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
    IPerson person = getPerson.GetPersion();
    return person.GetAge();
  }
  public static string GetNameViaPerson(this IGetPerson getPerson) {
    return getPerson.GetPerson().GetName();
  }
}

public class Person: IPerson, IGetPerson {
  private int Age {get;set;}
  private string Name {get;set;}
  public IPerson GetPerson() {
    return this;
  }
  public int GetAge() {  return Age; }
  public string GetName() { return Name; }
}

Maintenant, tout objet qui sait comment obtenir une personne peut implémenter IGetPerson, et il aura automatiquement les méthodes GetAgeViaPerson () et GetNameViaPerson (). À partir de ce moment, tous les codes de personne vont à IGetPerson, pas à IPerson, à l'exception des nouveaux ivars, qui doivent aller à la fois. Et en utilisant ce code, vous n'avez pas à vous soucier de savoir si votre objet IGetPerson est lui-même réellement un IPerson.

0
William Jockusch

Si X hérite de Y, cela a deux effets quelque peu orthogonaux:

  1. Y fournira les fonctionnalités par défaut pour X, le code pour X doit donc uniquement inclure des éléments différents de Y.
  2. Presque n'importe où un Y est attendu, un X peut être utilisé à la place.

Bien que l'héritage offre les deux caractéristiques, il n'est pas difficile d'imaginer des circonstances dans lesquelles l'une ou l'autre pourrait être utile sans l'autre. Je connais un langage .net que je connais possède un moyen direct d’implémenter le premier sans le second, bien que l’on puisse obtenir une telle fonctionnalité en définissant une classe de base qui n’est jamais utilisée directement et en ayant une ou plusieurs classes qui en héritent directement sans rien ajouter. new (ces classes pourraient partager tout leur code, mais ne seraient pas substituables les unes aux autres). Tout langage conforme au CLR, cependant, autorisera l’utilisation d’interfaces fournissant la deuxième caractéristique des interfaces (substituabilité) sans la première (réutilisation des membres).

0
supercat

Avec C # 8, vous avez pratiquement plusieurs héritages via l'implémentation par défaut des membres de l'interface:

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}
0
Ludmil Tinkov

Nous semblons tous nous diriger vers le chemin d’interface avec cela, mais l’autre possibilité évidente est, ici, de faire ce que OOP est censé faire, et de construire votre arbre de patrimoine ... (N'est-ce pas ce que la conception de classe est tout au sujet?)

class Program
{
    static void Main(string[] args)
    {
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

Cette structure fournit des blocs de code réutilisables et comment le code OOP doit-il être écrit?

Si cette approche particulière ne convient pas, nous créons simplement de nouvelles classes basées sur les objets requis ...

class Program
{
    static void Main(string[] args)
    {
        fish shark = new fish();
        shark.size = "large";
        shark.lfType = "Fish";
        shark.name = "Jaws";
        Console.WriteLine(shark.name);
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

public class aquatic : lifeform
{
    public string size { get; set; }
}

public class fish : aquatic
{
    public string name { get; set; }
}
0
Paul

je sais que je sais que même si ce n'est pas permis et ainsi de suite, parfois vous en avez vraiment besoin, alors:

class a {}
class b : a {}
class c : b {}

comme dans mon cas, je voulais faire cette classe b: Form (yep the windows.forms) classe c: b {}

provoquer la moitié de la fonction étaient identiques et avec l'interface u doit les réécrire tous

0
ariel