web-dev-qa-db-de.com

Mehrfachvererbung in C #

Da Mehrfachvererbung schlecht ist (was die Quelle komplizierter macht), liefert C # ein solches Muster nicht direkt. Aber manchmal wäre es hilfreich, diese Fähigkeit zu haben.

Zum Beispiel kann ich das fehlende Mehrfachvererbungsmuster mithilfe von Schnittstellen und drei Klassen wie dieser implementieren:

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

Jedes Mal, wenn ich einer der Schnittstellen eine Methode hinzufüge, muss ich auch die Klasse FirstAndSecond ändern.

Gibt es eine Möglichkeit, mehrere vorhandene Klassen in eine neue Klasse einzufügen, wie dies in C++ möglich ist?

Vielleicht gibt es eine Lösung mit einer Art Codegenerierung?

Oder es könnte so aussehen (imaginäre c # -Syntax):

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

Damit die Klasse FirstAndSecond nicht aktualisiert werden muss, wenn ich eine der Schnittstellen ändere.


BEARBEITEN

Vielleicht wäre es besser, ein praktisches Beispiel zu betrachten:

Sie haben eine vorhandene Klasse (zB einen textbasierten TCP Client basierend auf ITextTcpClient), den Sie bereits an verschiedenen Stellen in Ihrem Projekt verwenden. Nun haben Sie das Bedürfnis, eine Komponente Ihrer Klasse zu erstellen für Windows Forms-Entwickler leicht zugänglich sein.

Soweit ich weiß, haben Sie derzeit zwei Möglichkeiten, dies zu tun:

  1. Schreiben Sie eine neue Klasse, die von Komponenten geerbt wird und die Schnittstelle der TextTcpClient-Klasse unter Verwendung einer Instanz der Klasse selbst implementiert, wie in FirstAndSecond gezeigt.

  2. Schreiben Sie eine neue Klasse, die von TextTcpClient erbt und irgendwie IComponent implementiert (haben dies noch nicht wirklich ausprobiert).

In beiden Fällen müssen Sie pro Methode und nicht pro Klasse arbeiten. Da Sie wissen, dass wir alle Methoden von TextTcpClient und Component benötigen, ist es die einfachste Lösung, diese beiden Methoden in einer Klasse zu kombinieren.

Um Konflikte zu vermeiden, kann dies durch Codegenerierung geschehen, bei der das Ergebnis später geändert werden kann. Das Eingeben von Hand ist jedoch ein reines Ärgernis.

202
Martin

Da Mehrfachvererbung schlecht ist (was die Quelle komplizierter macht), liefert C # ein solches Muster nicht direkt. Aber manchmal wäre es hilfreich, diese Fähigkeit zu haben.

C # und die .net CLR haben MI nicht implementiert, weil sie noch nicht entschieden haben, wie es zwischen C #, VB.net und den anderen Sprachen zusammenwirken würde, nicht, weil "es die Quelle komplexer machen würde".

MI ist ein nützliches Konzept. Die unbeantworteten Fragen lauten wie folgt: "Was machen Sie, wenn Sie mehrere gemeinsame Basisklassen in den verschiedenen Superklassen haben?

Perl ist die einzige Sprache, mit der ich je gearbeitet habe, wo MI funktioniert und gut funktioniert. .Net wird es vielleicht eines Tages einführen, aber noch nicht. Die CLR unterstützt MI bereits, aber wie gesagt, darüber hinaus gibt es noch keine Sprachkonstrukte.

Bis dahin stecken Sie stattdessen mit Proxy-Objekten und mehreren Schnittstellen fest :(

118
IanNorton

Verwenden Sie einfach Komposition, anstatt zu versuchen, die Mehrfachvererbung zu simulieren. Sie können Interfaces verwenden, um zu definieren, aus welchen Klassen die Komposition besteht. Beispiel: ISteerable impliziert eine Eigenschaft vom Typ SteeringWheel, IBrakable impliziert eine Eigenschaft vom Typ BrakePedal , usw.

Anschließend können Sie die in C # 3.0 hinzugefügte Funktion Erweiterungsmethoden verwenden, um das Aufrufen von Methoden für diese implizierten Eigenschaften weiter zu vereinfachen, z.

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

Ich habe einen C # -Post-Compiler erstellt, der Folgendes ermöglicht:

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

Sie können den Post-Compiler als Visual Studio-Post-Build-Ereignis ausführen:

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

In derselben Assembly verwenden Sie es wie folgt:

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

In einer anderen Assembly verwenden Sie es wie folgt:

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

Ich würde das auch mögen - es ist das, was ich persönlich als Mix-In bezeichne, obwohl mir klar ist, dass es ein überladener Begriff ist. Ich möchte in der Lage sein, die Variable anzugeben, die zum Implementieren der Schnittstelle verwendet wird, mit der Option, meine eigene Implementierung für bestimmte Methoden bereitzustellen.

Ich habe darüber ausführlicher gebloggt - obwohl im Zusammenhang mit einer absichtlichen Überbewertung, was es in Bezug auf die Vererbung bedeuten könnte.

Ich sehe keinen Grund, warum dies nicht im C # -Compiler implementiert werden konnte - aber es ist ein weiteres bisschen Sprachkomplexität ...

14
Jon Skeet

Sie könnten eine abstrakte Basisklasse haben, die sowohl IFirst als auch ISecond implementiert und dann nur von dieser Basis erbt.

5
Joel Coehoorn

MI ist NICHT schlecht, jeder, der es (ernsthaft) benutzt hat, LIEBT es und es kompliziert den Code NICHT! Zumindest nicht mehr als andere Konstrukte können den Code komplizieren. Ungültiger Code ist ungültiger Code, unabhängig davon, ob MI auf dem Bild angezeigt wird oder nicht.

Wie auch immer, ich habe eine nette kleine Lösung für Mehrfachvererbung, die ich teilen wollte. http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog oder Sie können dem Link in meiner Signatur folgen ... :)

2
Thomas Hansen

Ja, die Verwendung von Interface ist problematisch, da jedes Mal, wenn wir eine Methode in die Klasse einfügen, die Signatur in das Interface eingefügt werden muss. Was ist auch, wenn wir bereits eine Klasse mit einer Reihe von Methoden, aber ohne Schnittstelle dafür haben? Wir müssen Interface manuell für alle Klassen erstellen, von denen wir erben möchten. Und das Schlimmste ist, dass wir alle Methoden in den Interfaces in der Child-Klasse implementieren müssen, wenn die Child-Klasse von der Multiple-Interface-Klasse erben soll.

Indem wir dem Entwurfsmuster für Fassaden folgen, können wir das Erben von mehreren Klassen mit Zugriffsmethoden simulieren. Deklarieren Sie die Klassen als Eigenschaften mit {get; set;} innerhalb der zu erbenden Klasse, und alle öffentlichen Eigenschaften und Methoden stammen von dieser Klasse. Instanziieren Sie im Konstruktor der untergeordneten Klasse die übergeordneten Klassen.

Zum Beispiel:

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


 }

mit dieser Struktur hat die Klasse Child Zugriff auf alle Methoden und Eigenschaften der Klassen Father und Mother, simuliert die Mehrfachvererbung und erbt eine Instanz der übergeordneten Klassen. Nicht ganz das Gleiche, aber es ist praktisch.

1
Yogi

Wenn Sie mit der Einschränkung leben können, dass die Methoden von IFirst und ISecond nur mit dem Vertrag von IFirst und ISecond interagieren dürfen (wie in Ihrem Beispiel), können Sie das tun, was Sie mit Erweiterungsmethoden verlangen. In der Praxis ist dies selten der Fall.

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

Die Grundidee ist also, dass Sie die erforderliche Implementierung in den Schnittstellen definieren ... dieses erforderliche Zeug sollte die flexible Implementierung in den Erweiterungsmethoden unterstützen. Immer wenn Sie "Methoden zur Schnittstelle hinzufügen" müssen, fügen Sie stattdessen eine Erweiterungsmethode hinzu.

1
Amy B

Mehrfachvererbung ist eines der Dinge, die im Allgemeinen mehr Probleme verursachen als lösen. In C++ passt es zum Muster, dass Sie genug Seil haben, um sich zu erhängen, aber Java und C # haben sich entschieden, den sichereren Weg zu gehen, Ihnen die Option nicht zu geben. Das größte Problem ist, was zu tun ist, wenn Sie erben mehrere Klassen mit einer Methode mit der gleichen Signatur, die der Erbe nicht implementiert hat. Welche Methode sollte er auswählen oder nicht kompilieren? Im Allgemeinen gibt es eine andere Möglichkeit, die meisten Dinge zu implementieren, die nicht auf Mehrfachvererbung beruhen .

0
tloach

Da die Frage der Mehrfachvererbung von Zeit zu Zeit auftaucht, möchte ich einen Ansatz hinzufügen, der einige Probleme mit dem Kompositionsmuster angeht.

Ich baue auf dem Ansatz von IFirst, ISecond, First, Second, FirstAndSecond auf, wie er in der Frage dargestellt wurde. Ich reduziere den Beispielcode auf IFirst, da das Muster unabhängig von der Anzahl der Interfaces/MI-Basisklassen gleich bleibt.

Nehmen wir an, dass mit MI First und Second beide von derselben Basisklasse BaseClass abgeleitet werden, wobei nur öffentliche Schnittstellenelemente von BaseClass verwendet werden.

Dies kann ausgedrückt werden, indem ein Container-Verweis auf BaseClass in der Implementierung von First und Second hinzugefügt wird:

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

Die Dinge werden komplizierter, wenn auf geschützte Schnittstellenelemente von BaseClass verwiesen wird oder wenn First und Second abstrakte Klassen in MI sind und ihre Unterklassen einige abstrakte Teile implementieren müssen.

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
}

Mit C # können verschachtelte Klassen auf geschützte/private Elemente ihrer enthaltenen Klassen zugreifen, sodass die abstrakten Bits aus der Implementierung von First verknüpft werden können.

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

Es gibt einige Probleme, aber wenn die tatsächliche Implementierung von FirstMethod und SecondMethod ausreichend komplex ist und die Anzahl der zugegriffenen privaten/geschützten Methoden moderat ist, kann dieses Muster helfen, das Fehlen einer Mehrfachvererbung zu überwinden.

0
grek40

In meiner eigenen Implementierung stellte ich fest, dass die Verwendung von Klassen/Interfaces für MI, obwohl "gute Form", eine massive Überkomplikation darstellt, da Sie die gesamte Mehrfachvererbung nur für ein paar notwendige Funktionsaufrufe einrichten müssen, und in meinem Fall: musste buchstäblich Dutzende Male überflüssig gemacht werden.

Stattdessen war es einfacher, statische "Funktionen, die Funktionen aufrufen, die Funktionen aufrufen" in verschiedenen modularen Varianten als eine Art OOP Ersatz zu erstellen. Die Lösung, an der ich arbeitete, war das "Zauber-System" für ein Rollenspiel, bei dem die Effekte stark Mix-and-Match-Funktionen aufrufen müssen, um eine extreme Vielfalt von Zaubersprüchen zu erhalten, ohne den Code neu zu schreiben, ähnlich wie beim Beispiel scheint darauf hinzuweisen.

Die meisten Funktionen können jetzt statisch sein, da ich nicht unbedingt eine Instanz für die Rechtschreiblogik benötige, während die Klassenvererbung im statischen Zustand nicht einmal virtuelle oder abstrakte Schlüsselwörter verwenden kann. Schnittstellen können sie überhaupt nicht verwenden.

Codierung scheint so IMO viel schneller und sauberer. Wenn Sie nur Funktionen ausführen und keine geerbten Eigenschaften benötigen, verwenden Sie Funktionen.

0
hydrix

Dies entspricht der Antwort von Lawrence Wenham. Je nach Anwendungsfall kann dies jedoch eine Verbesserung darstellen oder auch nicht - Sie benötigen die Setter nicht.

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

Jetzt kann jedes Objekt, das weiß, wie eine Person abgerufen wird, IGetPerson implementieren, und es verfügt automatisch über die Methoden GetAgeViaPerson () und GetNameViaPerson (). Von diesem Punkt an geht der gesamte Personencode an IGetPerson und nicht mehr an IPerson, mit Ausnahme von neuen ivars, die in beide geschrieben werden müssen. Und wenn Sie solchen Code verwenden, müssen Sie sich keine Gedanken darüber machen, ob Ihr IGetPerson-Objekt selbst tatsächlich eine IPerson ist oder nicht.

0

Wenn X von Y erbt, hat dies zwei orthogonale Effekte:

  1. Y stellt die Standardfunktionalität für X bereit, sodass der Code für X nur von Y verschiedene Elemente enthalten muss.
  2. Fast überall wäre ein Y zu erwarten, stattdessen kann ein X verwendet werden.

Obwohl die Vererbung beide Merkmale vorsieht, ist es nicht schwer, sich Umstände vorzustellen, unter denen eines ohne das andere nützlich sein könnte. Keine mir bekannte .net-Sprache hat eine direkte Möglichkeit, die erste ohne die zweite zu implementieren, obwohl man eine solche Funktionalität erhalten könnte, indem man eine Basisklasse definiert, die niemals direkt verwendet wird, und eine oder mehrere Klassen hat, die direkt von ihr erben, ohne etwas hinzuzufügen neu (solche Klassen könnten ihren gesamten Code gemeinsam nutzen, wären jedoch nicht gegeneinander austauschbar). Jede CLR-konforme Sprache ermöglicht jedoch die Verwendung von Schnittstellen, die das zweite Merkmal von Schnittstellen (Substituierbarkeit) ohne das erste Merkmal (Wiederverwendung von Mitgliedern) bieten.

0
supercat

Mit C # 8 haben Sie jetzt praktisch Mehrfachvererbung über die Standardimplementierung von Schnittstellenelementen:

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

Wir scheinen alle damit den Pfad der Benutzeroberfläche entlang zu gehen, aber die offensichtliche andere Möglichkeit besteht darin, das zu tun, was OOP tun soll, und Ihren Vererbungsbaum aufzubauen ... - (Ist das nicht das, worum es beim Klassendesign geht?)

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

Diese Struktur bietet wiederverwendbare Codeblöcke und bestimmt, wie OOP Code geschrieben werden sollte?

Wenn dieser spezielle Ansatz nicht in die Rechnung passt, erstellen wir einfach neue Klassen basierend auf den erforderlichen Objekten ...

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

ich weiß, ich weiß, obwohl es nicht erlaubt ist und so weiter, irgendwann brauchst du es wirklich so für die:

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

wie in meinem Fall wollte ich diese Klasse machen b: Form (yep the windows.forms) Klasse c: b {}

weil die hälfte der funktion identisch war und mit interface u alle neu geschrieben werden müssen

0
ariel