C# Spaßfrage - ist das Mehrfachvererbung ?

Status
Für weitere Antworten geschlossen.

Micke

Lt. Junior Grade
Registriert
Nov. 2020
Beiträge
505
gegeben sind die Fundamente:
Code:
public abstract class Säugetier
{
    public virtual int AnzahlBeine => 4;

    public void Rennen()
    {
        if (AnzahlBeine > 0)
            Console.WriteLine("rennen");
    }
}

public interface IMeinHaustier
{
    bool KannSchwimmen => true;

    void Schwimmen()
    {
        if (KannSchwimmen)
            Console.WriteLine("schwimmen");
    }
}
Diese werden verwendet in:
Code:
public class Pferd : Säugetier, IMeinHaustier
{
}

public class Hamster : Säugetier, IMeinHaustier
{
    public bool KannSchwimmen => false;
}

Pferde und Hamster schwimmen & rennen mittels
Code:
    void Main()
    {
        Pferd p = new Pferd();
        p.Rennen();
        IMeinHaustier h = new Hamster();
        h.Schwimmen();
    }
Und, Meinungen🙂 ?
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Mutex
Lösung
Interessant. Ich bin vor allem in Java bzw. seit ein paar Jahren in Kotlin unterwegs, dachte aber, dass C# sich da wie Java verhält. Java liefert einen Compiler-Error, wenn mehrere Interface Default Methods miteinander im Konflikt stehen. Man muss die Methode dann in der Klasse implementieren und die gewünschte(n) explizit aufrufen. Wenn die Default Methods eindeutig sind, hat die Klasse sie, d.h. man kann sie über eine Variable mit dem Typ der Klasse aufrufen. Antwort #2 überrascht mich.
Ergänzung ()

Der Einwand von tollertyp bezüglich des Abdriftens in Compiler-Details ist nicht ganz unberechtigt. Ich wollte eigentlich noch auf die ursprüngliche Frage zurückkommen, habe aber aufgehört. 🙃

Die Definition von Mehrfachvererbung...
@tollertyp: Also damit erspare ich mir jede weitere Diskussion mit dir. Dunning Krueger in Bestform bei dir.
Keine Ahnung, ich hatte halt eine Beobachtung gemacht, sie dir mitgeteilt, doch anstatt meine Beobachtungen und Interpretationen zu korrigieren und mich vielleicht auf meine Fehler oder vielleicht ein verstecktes Detail hinzuweisen, was ich dabei übersah und falsch schlussfolgerte, kann ich den direkten Bezug von meiner Person zum Dunning Krueger Effekt und deine Einschätzung, dass ich da in Bestform bin, nicht so ganz folgen. Aber vielleicht ist das genau eines der Kennzeichen des Dunning Krueger Effekts, also vorab schonmal danke für diesen Hinweis.

@tollertyp: Dein Programmierverständnis reicht offensichtlich nicht aus, um das selbst zu erkennen. Und die Mühe nachzudenken machst du dir scheinbar auch nicht.
Und noch ein kleines persönliches Detail von mir, um deine Aussage auch zu bestätigen, aktuell ist es wirklich so, dass ich von Programmieren absolut keine Ahnung mehr habe, da ich mich mittlerweile beruflich wo anders hinentwickelt habe. In diesem jetzigen Tätigkeitsfeld ist es nicht mehr erforderlich, die genauen Details zu kennen und trotzdem lächele ich am Ende des Monats, wenn mein jetziger Arbeitgeber eine magische Zahl in einer Datenbank abspeichern lässt, die stetig steigt... ;-) So gesehen eine WinWin Situation...

Und ja, im Verlauf dieses Threads habe ich bereits so einige Fehler gemacht... Aber ich finde den Verlauf echt Klasse, weil hier doch so einiges an nützlichen Informationen drin steckt, also GO!

Ich kllnke mich auch aus, doch danke für die nette Diskussion mit dir, @tollertyp. Es hat mir die Augen geöffnet und lässt mich auch wieder eine neue Sichtweise berücksichtigen...
 
Zuletzt bearbeitet:
Ein Aspekt, der, glaube ich, noch nicht erwähnt wurde ist: wie compiliert man Mehrfachvererbung? Zugriff auf Instance-Variablen wird typischerweise durch Pointer (this) + Offset ersetzt. Bei einfacher Vererbung kann man alle Variablen entlang der Vererbungskette einfach hintereinander in den Speicher legen und der Offset ist beim Compilieren bekannt (Interfaces können keine Variablen haben). Bei Mehrfachvererbung wird das komplizierter. Bei Methoden geht es offensichtlich auch mit Interfaces. Wie das im Detail funktioniert, weiß ich nicht genau, aber zumindest bei C++ liegen grob beschrieben die Pointer zu Methoden an einem bestimmten Index in der sogenannten Virtual Table, und eine Instanz enthält einen Pointer zur dieser. Dass man das nur mit Methoden so macht, hat vor allem Performance-Gründe.

Um mehr darüber zu erfahren, einfach mal nach "c++ multiple inheritance memory layout" oder "c++ virtual methods" googlen.

Beim Design von C# und Java hat man es sich halt (sicherlich zurecht) einfacher gemacht, und Default Interface Methods kamen ja auch erst später.
 
  • Gefällt mir
Reaktionen: Rossibaer und BeBur
Hi H4110,

danke, dass du hier am Thread teilnimmst.

Ist auch ein interessanter Aspekt, den du da ansprichst.

Diese Vererbungsmagie hinsichtlich Speicherlayout wird erst zur Laufzeit passieren, wenn der JIT Compiler dann die plattformunabhängigen MSIL Instruktionen in nativen Code kompiliert. Ebenso wird dann unter Umständen nochmal nach Bedarf optimiert und der Garbage Collector tut während seiner Arbeit auch gern mal Objekte im Speicher hin und her schieben, wie es ihm gerade passt. Es besteht ne gute Chance, dass da an entsprechender Stelle ein Blick in den Runtime Code von .NET (seit einigen Jahren als Open Source auf GitHub verfügbar) des Rätsels Lösung offenbaren würde, denn es ist müsig sich die Informationen aus Fachbüchern anzulesen, da sie als eine Form von Dokumentation ebenso schnell veraltet sind, ab dem Zeitpunkt wo der letzte Punkt im Text gesetzt wurde. Der Quellcode der .NET Runtime ist da bekanntlich exakter.
 
Da das hier mal immer weiter zu einem persönlichen Grabenkampf von der Seite des tollen Typen auszuarten scheint und mittlerweile schon eine sehr persönliche Sache wird...

Hier mal eine persönliche Beobachtung auch von meiner Seite:
@tollertyp: also mittlerweile kündigst du bereits zum zweiten Mal deinen Abschied an, doch irgendwie scheint dich diese Sache hier sehr zu faszinieren. Es scheint dich doch schon brennend zu interessieren, was hier im Thread abläuft. Ist das der Grund, warum du eine so wahrlich beeindruckende Anzahl von 11.482 Beiträgen am 06.11.2023 um 22:31 erreicht hast, obwohl du erst seit Februar 2020 dabei bist? Also aus meiner Sicht eine richtig gute Performance deinerseits, Respekt!

Ich habe da mal paar Fragen an dich (gerne auch per PM beantwortbar):

Wieviele Beiträge schreibst du so am Tag oder pro Woche?
Laut meiner Rechnung dürften das so Pi mal Daumen 8 Beiträge pro Tag sein, d.h. noch ein Kommentar hier im Thread und du hast erfolgreich dein Tagessoll erreicht.

In wievielen Unterforen bist du so durchschnittlich aktiv am diskutieren, z.B. pro Tag?

Aber gerne würde ich mal was teilen. Ich habe da mal so ein paar Untersuchungen meinerseits gemacht und würde sie gern mal zur Diskussion stellen...

TollerTyp.png


ich war mal so frei und habe 2 Interfaces angelegt:
1. ITollerTyp (der wohlgemerkt in der Java-Ecke nach eigenem Bekunden sein eigentliches zu Hause hat)
2. IRossibaer (der wohlgemerkt nicht mehr so ganz im Programmieren fit ist)

Irgendwo gibt es da noch eine Klasse ComputerBaseMember, bei der nun beide Interfaces hinzugefügt wurden.

Also, ich habe da mal eine Instanz eines Objektes von der Klasse ComputerBaseMember erzeugt und nannte die entsprechende Variable dann einfach mal member.

Im weiteren Schritt machte ich eine Zuweisung von member zu einer Variable vom Typ ITollerTyp und nannte sie mal @tollertyp.
Gleiches machte ich mit dem Interface IRossibar und nannte sie einfach sinnigerweise @Rossibaer.

Darauf lies ich mir mal einfach ein paar Ausgaben auf der Konsole generieren:
In Zeile 7 und 8 sind dann die entsprechenden Aufrufe von Console.WriteLine().
In Zeile 11 und 12 habe ich mal die Ausgabe in Form eines Kommentars eingefügt.

Dann dachte ich mir, hey, wie wäre es, wenn ich einfach mal nur member in einer Konsolenausgabe versuche den Wert der readonly Property auszugeben.

Beobachtung #1: Der Compiler gab mir einen Fehler CS1061 - und sagte mir, dass die Property YearsOfMemberShip bei der Klasse ComputerBaseMember nicht implementiert ist.

Überlegung #1: Ok, ich hatte sie tatsächlich nicht in der Klasse implementiert, aber der Compiler hatte keine Probleme damit, die Klasse mit 2 Interfaces, die beide YearsOfMemberShip definiert haben, zu kompilieren. Erst als ich versuchte, über die Variable member auf die Property zuzugreifen, gabs den Fehler CS1061. Während ich bei den Variablen vom Typ ITollerTyp und Typ IRossibaer problemlos auf die Property YearsOfMemberShip zugreifen konnte, ist es bei der Variable member nicht möglich.

Frage #1: Kann es sein, dass diese Implementierungen der Interfaces vielleicht nur explizit ansprechbar sind?

Antwort: #1: Es deutet darauf hin.

Frage #2: Kann es sein, dass bei beiden Interfaces der Compilerfehler CS1061 nur dann auftritt, wenn die Property den gleichen Namen hat?

Antwort #2: Ich habe beide Properties unterschiedlich benannt und siehe da, selbst wenn sie unterschiedliche Namen hatten, kam der Fehler. Aber wenn ich mir halt den Fehler vom Text auch durchlese, passt es für mich ins Bild, da auch der Compiler halt sagt, die Property ist bei ComputerBaseMember nicht implementiert.

Schlussfolgerung #1: da es sich eher um explizite Implementierungen von den Interface Membern zu handeln scheint, sollte das Diamond-Problem also wahrscheinlich kein Problem sein. Soweit korrekt?

Schlussfolgerung #2: da die Klasse member die Property YearsOfMemberShip nicht implementiert, laut Fehler vom Compiler, dann scheint hier nicht wirklich eine Vererbung im Spiel zu sein, denn wenn doch etwas vererbt wurde, dann sollte doch entweder die eine oder die andere Implementierung in irgendeiner Form direkt über die Variable member erreichbar sein. Soweit korrekt?

Ich probierte dann das Spiel mit member und verwendete das Schlüsselwort "dynamic" und nannte dann diese dynamicMember, denn lag sehr nah, dass wir doch alle sehr dynamisch sind und auf unterschiedliche Reize reagieren.
Ergebnis: Im Grunde das gleiche, was mir der Compiler bei Verwendung von member.YearsOfMemberShip schon verboten hatte, nur dass dies dann per Laufzeit geschah (RuntimeBinderException). Wirklich interessant! Auch zur Laufzeit des Threads trat eine Ausnahme auf, weil keine Bindung erfolgen konnte. Soweit ich weiß, kommt doch bei den dynamischen Dingen doch auch irgendwann der Versuch einer Bindung zu Stande in dem zum Beispiel mal einfach reflektiert wird, oder?

Egal, weitere Untersuchungen folgen morgen... Den Quellcode des Garbage Collector schau ich mir sicherlich dann mal genauer an, bevor ich dann Gefahr laufe, im Mehr der Beiträge des tollen Typen unterzugehen, der ja bekanntlich auf ner sicheren Insel sitzt, während ich schwimme.

Edit: @tollertyp Nochmal kurz geschaut, meine Erwähnung des Garbage Collector bezog sich auf die Tatsache, dass er auch auf die MethodTable, der von ihm verwalteten Objekt-Instanzen während des Verschiebens im Speicher Einfluß nimmt. Gibt da so eine sehr interessante RawSetMethodTable() Funktion, die im gc.cpp an einigen Stellen im GC aufgerufen wird. Da H4110 das Thema ansprach, wie wohl das mit den Methoden im .NET funktioniert und sich dann auf eine VTable aus dem C++ Umfeld mit Function-Pointern bezog, war halt mein Statement gerade auf diese Sache bezogen und hatte erstmal nichts per se mit einer vermeintlichen Mehrfachvererbung zu tun, wie du ja treffsicher in deiner unnachahmlichen Art bereits festgestellt hast oder zumindest implizit versuchtest auszudrücken.
Aber auch da wieder eine Fehlannahme meinerseits, da ich davon ausging, dass ich jetzt nicht unmittelbar jeden kleine Textschnipsel erst als Zitat einfügen muss, damit auch so hochperformante Multithread-Poster, wie du, dem Ganzen noch folgen können. In der Tat eine Wall Of Text zu produzieren ist halt doch etwas anstrengender, als mal kurz Bullshit-Bingo zu rufen und dann schnell wieder bei einem Win11 Thread weiter zu posten um dann grob gefühlte 26 Beiträge an einem Tag einzuheimsen... Gibts eigentlich nen Preis wenn man sagen wir mal so 100.000 Beiträge in einem Jahr schafft? Gibts da einen Amazon Gutschein für? Würde mich mal interessieren.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: H4110
Interessant. Ich bin vor allem in Java bzw. seit ein paar Jahren in Kotlin unterwegs, dachte aber, dass C# sich da wie Java verhält. Java liefert einen Compiler-Error, wenn mehrere Interface Default Methods miteinander im Konflikt stehen. Man muss die Methode dann in der Klasse implementieren und die gewünschte(n) explizit aufrufen. Wenn die Default Methods eindeutig sind, hat die Klasse sie, d.h. man kann sie über eine Variable mit dem Typ der Klasse aufrufen. Antwort #2 überrascht mich.
Ergänzung ()

Der Einwand von tollertyp bezüglich des Abdriftens in Compiler-Details ist nicht ganz unberechtigt. Ich wollte eigentlich noch auf die ursprüngliche Frage zurückkommen, habe aber aufgehört. 🙃

Die Definition von Mehrfachvererbung wurde ja schon genannt (mehrere Basisklassen), aber was, wenn man die aufweicht und Variablen und Methoden getrennt betrachtet? Variablen sind klar: keine Mehrfachvererbung in C# (und Java).

Bezüglich Methoden mal ein Beispiel in Python:
Python:
class A:
    def foo(self):
        print("a", end="")


class B(A):
    def foo(self):
        print("h", end="")
        super().foo()


class C(A):
    def foo(self):
        print("o", end="")
        super().foo()


class D(B, C):
    def foo(self):
        print("W", end="")
        super().foo()
        print("!")


D().foo()

Das ist ein Diamant und er funktioniert - alle foo() werden ausgeführt, bei super() gibt es keine Konflikte. Ich würde sagen, das ist auf jeden Fall Mehrfachvererbung.

Im Vergleich schneidet sogar C++ schlecht ab. Es hat kein super Keyword, d.h. wenn man eine Methode in einer abgeleiteten Klasse überschreibt, und "super" aufrufen will, muss man immer explizit die Klasse angeben.

Mit Interface Default Methods sieht es bei Java und Kotlin (und ich dachte auch C#) eigentlich ähnlich aus. Es gibt ein super Keyword, was ein Vorteil gegenüber C++ ist, aber bei Konflikten muss man explizit werden. Nachteil gegenüber C++ ist, dass Default Methods nur indirekt mit Instanz-Variablen arbeiten können, indem das Interface, zu dem sie gehören, Getter/Setter definiert, die von der Klasse implementiert werden müssen.

Es ist ein guter Kompromiss, um einige der Vorteile von Mehrfachvererbung ohne die damit einhergehende Komplexität und Nachteile zu haben.

Aber letztendlich, meine Antwort auf die ursprüngliche Frage: nein, das ist keine Mehrfachvererbung. 😛
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Rossibaer und Micke
@H4110

wenn ich jetzt dein Python-Beispiel betrachte , mache ich gerade in meinem Kopf einen klassischen Spagat. Cooles Ding, danke dir!

Idee #1: Schaue ich mir die Vererbung an, dann würde ich sagen, dass am Ende "Whaoa!" rauskommen könnte, weil folgende naive Überlegungen meinerseits, wenn der Ablauf mit D.foo() beginnt:
1. Zeile 20 printed mir "W"
2. Zeile 21 durchläuft jetzt Zeile 8 und printed mir ein "h", soweit so gut
3. Zeile 9 hätte ich jetzt wegen der Vererbungslinie erwartet, dass er in Zeile 3 geht und mir ein "a" prompted
4. da A.foo() keine weitere Basisklassenmethode aufruft wäre hier Schluß
5. also zurück zum Anfang und nun die 2. Basisklasse C abfrühstücken (so mein Gedanke!)
6. Zeile 14 printed das "o"
7. da C dann A auch wieder beerbt, springt es zu Zeile 3 und printed "a"
8. ganz zum Schluß kommt noch das "!" in Zeile 22

Annahme #1: Ich fühlte mich auf der sicheren Seite und dachte, ja klar "Whaoa!", doch tatsächlich ging mir nicht der Hinweis auf den Diamanten aus dem Kopf und ich war neugierig, ob das mit dem übereinstimmte, was es dann tatsächlich druckt.

Ergebnis #1: Tatsächlich kam dann "Whoa!" raus. Cool, meine naiven Überlegungen und Annahme #1 waren Bullshit! Ich liebe es! Und "Whoa!" als ein Ausrduck von Verwunderung/Überraschung oder auch als weniger gebräuchliches Kommando ein reitendes Pferd zu bremsen, machte es für mich wesentlich mehr Sinn, als ein Wort, dass ich vom gedachten Klang Richtung Hawaii verorten würde. :-D

Schlussfolgerung #1: Im Moment wo, in Klasse D dann 2 Basisklassen angegeben werden, wurde quasi die "Vererbungslinien" zur Basisklasse A über D -> B -> A und D -> C -> A bei der Ausführung dann tatsächlich "aufgebrochen" und stattdessen ein Weg D -> B -> C -> A gegangen, was für mich wiederum heißt, Jo, dat sieht nach 'nem Diamanten aus.

Ebenso versuche ich gerade so ein bissel zu überlegen, was da zur Hölle eigentlich passierte. Irgendwie glaube ich mich zu erinnern, dass bei dem ganzen Vererbungskram dann zur Laufzeit bei der Erzeugung der Objektinstanz immer schön irgendwelche Tabellen bzgl. der Funktionspointer gepflegt werden. D.h. zuerst werden von der Basisklasse die enthaltenen Methoden, zumindest deren Funktionszeiger, in einer Tabelle "registriert", dann gehts in der Vererbungslinie zur ableitenden Klasse und es erfolgt ein Update dieser Tabelle, wenn dann eine Methode durch die ableitenden Klasse überschrieben wird, führt das mindestens zu einem Überschreiben des Funktionszeigers in der Tabelle und der neue Funktionszeiger, wird dann abgespeichert. Zusätzlich muss allerdings auch der ursprüngliche Funktionszeiger noch vorhanden sein, da es ja auch ein schönes "Feature" wäre, wenn im späteren Verlauf eben diese Basisimplementierung aufgerufen werden kann. z.B. ist das halt bei C# durch den Aufruf der Methode referenziert über "base" oder im Falle von Python über "super()"
möglich.

nochmal zurück zu meiner Idee #1. Aufgrund der verschiedenen Vererbungen, die hier stattfinden, werden die Funktionszeiger in der Tabelle während der "Abarbeitung" der einzelnen Vererbungslinien nacheinander überschrieben, wo final dann durch die Reihenfolge, B dann C , bei den Basisklassen von Klasse D der Letzte gewinnt und das ist C, somit ist dann der "Bruch" in meiner Idee #1 tatsächlich auch irgendwie im Kopf nachvollziehbar! Habe ich etwas übersehen?

@H4110
Du bist ein toller Typ, denn sowas macht mir Spass und hast natürlich recht, der Ausflug über das Compilerzeug ist zwar auch ne nette Sache, aber im Grunde war der Ursprung eigentlich die Vermutung, dass es in C# mittlerweile eine Mehrfachvererbung oder zumindest ein ähnlich anmutendes Feature gibt.

Und trotzdem bleib ich dabei, dass in C# der GC (Laufzeitkomponente, kein Compilerding) auch ein klein wenig mitzureden hat, was die Methodentabellen der Objektinstanzen betrifft, da Instanzen vom GC immer wieder während der Laufzeit im Speicher hin und her geschoben werden, sich dadurch Adressen ändern und genau dann gehts auch an diese Tabellen und es ist nicht nur einfach eine Speicherblock A nach B Kopieraktion! Da passiert wesentlich mehr. Aber im Grunde macht der GC hoffentlich auch nur Sachen, wo er sicher sein kann, dass die Funktionalität des Programms immer noch gewahrt bleibt.
 
Zuletzt bearbeitet:
Das ist so, weil Interfaces eben nur ein Verhalten vorgeben, also eine oder mehrere Methoden. Wie diese Methoden implementiert werden und welche Variablen oder sonstigen Ressourcen dafür benötigt werden, ist Sache der implementierenden Klasse. Ungefähr so wie ein Druckertreiber, der einen "Print(Dokument)" Befehl exponieren muss, aber welche Einstellungen er dazu benötigt ist Sache des Treibers und nicht des bedienenden Programms, welches einfach nur den Print-Befehl aufruft und das Dokument übergibt. Ein PDF-Drucker wird beispielsweise einen Dateinamen haben, ein Drucker mit Duplex-Einheit eine Einstellung für Duplex-Druck und so weiter und so fort. Word, welches den Druck nur auslöst, ist das aber einerlei.
 
@Micke: von Schnittstellen kann mehrfach geerbt werden, aber sie vererben keine MemberVariablen.

Sprichst du bei "MemberVariablen" von "Feldern" innerhalb einer Klasse?

Wenn ja, dann ok, denn so wie ich das sehe, kann zwar ein Interface mittlerweile halt Properties und Methoden auch mit einer Default-Implementierung versehen, nur eine Wiederholung des hier oft geschriebenen...

Andererseits stellt sich mir die Frage, ob es wirklich notwendig ist Felder zu benutzen? Im Kern geht's doch darum, eine Information abzuspeichern, die länger lebt als die Zeit der Ausführung einer Methode oder eines Blocks und nur innerhalb der Instanz verfügbar ist?

Da käme mir jetzt noch der Gedanke, dass ich evtl. auch genauso gut Lambdas verwenden könnte, um eine Information in einer lokalen Variablen abzulegen, die dann länger leben könnte, als der umschließende Block, oder? Wenn das geht, dann braucht es keine Felder in der Instanz, die mitvererbt werden sollen. ist bissel von hinten durch die Brust geschossen, aber eigentlich auch mal was Spannendes....
 
Rossibaer schrieb:
Sprichst du bei "MemberVariablen" von "Feldern" innerhalb einer Klasse?
ja
Rossibaer schrieb:
Andererseits stellt sich mir die Frage, ob es wirklich notwendig ist Felder zu benutzen? Im Kern geht's doch darum, eine Information abzuspeichern, die länger lebt als die Zeit der Ausführung einer Methode oder eines Blocks
ja, und das geht über's Interface nicht (außer via static). Auch nicht über eine Property. Zum Ausprobieren kannst du versuchen mein Code Bsp. so umzubauen, daß du den Wert setzen kannst.
Code:
  IMeinHaustier h = new Hamster();
  h.KannSchwimmen = ....
 
Zuletzt bearbeitet:
Jein, denn es hängt davon ab, was du statisch machst, siehe da:

C#:
// Program.cs
using System.Runtime.CompilerServices;

DoSomeStuff();
GC.Collect();
Console.WriteLine($"11. ({nameof(Program)})     Instances count    : {IStone.InstancesCount}");
return;

void DoSomeStuff()
{
    Console.WriteLine($" 1. ({nameof(DoSomeStuff)}) Instances count    : {IStone.InstancesCount}");
    IStone basalt = new Stone();
    IStone granite = new Stone();
    basalt.WeightInKg = 10.5f;
    Console.WriteLine($" 2. ({nameof(DoSomeStuff)}) Basalt weight set  : {basalt.WeightInKg} kg");
    Console.WriteLine($" 3. ({nameof(DoSomeStuff)}) Instances count    : {IStone.InstancesCount}");
    basalt.WeightInKg = 20.5f;
    Console.WriteLine($" 4. ({nameof(DoSomeStuff)}) Basalt weight set  : {basalt.WeightInKg} kg");
    Console.WriteLine($" 5. ({nameof(DoSomeStuff)}) Instances count    : {IStone.InstancesCount}");
    granite.WeightInKg = 30.5f;
    Console.WriteLine($" 6. ({nameof(DoSomeStuff)}) Granite weight set : {granite.WeightInKg} kg");
    Console.WriteLine($" 7. ({nameof(DoSomeStuff)}) Instances count    : {IStone.InstancesCount}");
    granite.WeightInKg = 40.5f;
    Console.WriteLine($" 8. ({nameof(DoSomeStuff)}) Granite weight set : {granite.WeightInKg} kg");
    Console.WriteLine($" 9. ({nameof(DoSomeStuff)}) Basalt weight get  : {basalt.WeightInKg} kg");
    Console.WriteLine($"10. ({nameof(DoSomeStuff)}) Instances count    : {IStone.InstancesCount}");
}

public interface IStone
{
    public float WeightInKg
    {
        get => Fields.GetOrCreateValue(this).WeightInKg;
        set => Fields.GetOrCreateValue(this).WeightInKg = value;
    }

    public static int InstancesCount => Fields.Count(); // ist nur zur Prüfung, ob der GC zuschlägt

    private static readonly ConditionalWeakTable<IStone, StoneFields> Fields = new();
    private class StoneFields
    {
        // um beliebige Instanzen-Felder erweiterbar, Properties siehe dann IStone.WeightInKg
        public float WeightInKg;
    }
}

public class Stone : IStone { }

da ich Hamster liebe und sie sich nur für paar Minuten an der Wasser-Oberfläche halten können, habe ich mal lieber paar schwere Steine gehoben...

Anmerkungen zum Code:

1. Ich wollte auch prüfen, dass paar interne Objektinstanzen auch wirklich aufgeräumt werden, sobald die Steine weg sind. Deswegen einfach immer wieder die Counts in der Ausgabe.

2. GC hat im Debugbuild die unangenehme Eigenschaft Objektinstanzen erst beim Verlassen der Methode und nicht beim Verlassen des Blocks freizugeben.

3. ConditionalWeakTable ist mein Mittel der Wahl, weil sie Objekt-Instanzen eher lose referenziert, sodaß da der GC halt die Möglichkeit hat auch aufzuräumen

4. Wenn du jetzt weitere Felder innerhalb deiner Interface-Implementierung brauchst, dann einfach die interne Klasse StoneFields (ab Zeile 38) um weitere Felder ergänzen und analog zur IStone.WeightInKg Property verwenden

5. Meine ursprüngliche Idee mit Lambda-Zeug rumzufummeln, hat nicht funktioniert...

Zusatz zur bisherigen Diskussion:

1. Ich hatte auch mal noch bissel, was mit den Default-Implementierungen von Interfaces rumgespielt und musste feststellen, sobald ne Implementierung in der Klasse stattfindet, dann sind alle betroffenen Default-Implementierungen für die Nüsse. (kann sein dass das schon mal kam...)

2. das Diamant Dingens von @H4110 in Python lässt sich auch über Interfaces in C# realisieren, doch da will der Compiler das Schlüsselwort "base" nicht erlauben, this gibt es allerdings, d.h. man wird nicht rumkommen dann "this" in das passende Basisinterface zu casten und dessen Methode/Property zu verwenden, was ich übrigens sehr gut finde, da man so im Code unmittelbar sieht, was abgeht und nicht rät, was wohl intern ablaufen könnte. (siehe Beitrag #27)

@Micke: Ich hoffe ich konnte dir jetzt eine mögliche Alternative zum Thema Instanzenfelder in Interface Implementierungen zeigen... (sollte allerdings erst ab VS 2022 funktionieren, ggf. kann ja jemand das mit älteren VS bzw. C# Compilern checken)
 
Zuletzt bearbeitet:
also so ganz verstehe ich deine Antwort jetzt nicht, vielleicht kannst du es genauer spezifizieren.

Ich hatte halt folgende Anforderung da rein interpretiert:

1. Eine Instanz-Property mit getter und setter im Interface als Default zu implementieren
2. Instanz-Property wäre nicht in der Klasse implementiert, weil gerne ähnliches Verhalten wie bei einer Mehrfachvererbung gewünscht ist und bereits der Compiler hier einen Strich durch die Rechnung machen kann (Verlauf des gesamten Thread)

Du kannst nach wievor mit den Instanzen deiner Objekte der Klasse Stone arbeiten, wenn du halt den Weg über die Interface Defaultimplementierung gehst, jedoch wie ich bereits einmal schrieb, sobald du eine Default-Implementierung hast und dann in der Klasse, die dieses Interface implementiert, die Default-Implementierung überschreibst, ist es vorbei.

Vielleicht habe ich das jetzt falsch aufgefasst, also was sind jetzt genau deine Anforderungen?

Auch nicht über eine Property.
Zum Ausprobieren kannst du versuchen mein Code Bsp. so umzubauen, daß du den Wert setzen kannst.
siehe IStone.WeightInKg (getter + setter) -> also ja, den Wert kann ich setzen, s.u.

Also was willst du wirklich?
Meinst du so?

C#:
using System.Runtime.CompilerServices;

IMeinHaustier schnuppi = new Hamster();
IMeinHaustier schnuffi = new Hamster();

schnuppi.Name = "Schmatzi";
schnuffi.Name = "Schnuffi";
schnuppi.Schwimmen();
schnuffi.Schwimmen();
schnuppi.KannSchwimmen = false;
schnuppi.Schwimmen();
schnuffi.Schwimmen();
schnuffi.KannSchwimmen = false;
schnuppi.Schwimmen();
schnuffi.Schwimmen();
return;
public abstract class Säugetier
{
    public int AnzahlBeine => 4;

    public void Rennen()
    {
        if (AnzahlBeine > 0)
            Console.WriteLine("rennen");
    }
}
public class Hamster : Säugetier, IMeinHaustier
{
}
public interface IMeinHaustier
{
    bool KannSchwimmen
    {
        get => Fields.GetOrCreateValue(this).KannSchwimmen;
        set => Fields.GetOrCreateValue(this).KannSchwimmen = value;
    }

    string Name
    {
        get => Fields.GetOrCreateValue(this).Name;
        set => Fields.GetOrCreateValue(this).Name = value;
    }

    void Schwimmen()
    {
        if (KannSchwimmen)
            Console.WriteLine($"{Name} schwimmt.");
        else
            Console.WriteLine($"{Name} will nicht schwimmen.");
    }
 
    private static readonly ConditionalWeakTable<IMeinHaustier, HaustierFields> Fields = new();
    private class HaustierFields
    {
        public bool KannSchwimmen = true;
        public string Name;
    }
}

Mit dieser Ausgabe dann?

Code:
Schmatzi schwimmt.
Schnuffi schwimmt.
Schmatzi will nicht schwimmen.
Schnuffi schwimmt.
Schmatzi will nicht schwimmen.
Schnuffi will nicht schwimmen.

Und noch ein Edit:
Willst du über das Interface den Wert von KannSchwimmen setzen können?
Wilst du über die Instanz verhindern das Hamster.KannSchwimmen gesetzt werden kann?

z.B.
C#:
Hamster echterHamster = new Hamster();
IHamster vertragsHamster = realerHamster;

echterHamster.KannSchwimmen = true; // Hier soll der Compiler dir etwa sagen: NÖ, geht nicht?
vertragsHamster.KannSchwimmen = true; // Hier soll alles Supi sein und der Compiler sagt: YES?

Wenn du beide Fragen mit JA beantwortest, dann komme ich zu dem Ergebnis: WHAT?
Ich dachte eigentlich, dass es genau umgekehrt ist, also das ein Vertrag (Interface) eher einschränkender Natur ist und die erbrachte Leistung (Objektinstanz) dann u.U. mehr kann...

Und zu guter Letzt ... die Spezialfragen:
Störst du dich an dem Inhalt des Interfaces IHaustier, wo tatsächlich ein privates statisches Feld vom Typ ConditionalWeakTable ist?
Vermutest du etwa, dass dadurch die Properties nicht den Character einer Instanz-Property haben, sondern einer statischen Property?

Gerade triggerst du mich, weil ich gern deinen UseCase verstehen würde!

es geht ja darum mit Objekten zu arbeiten
1. Klasse = Bauplan eines Hauses
2. Objekte = Häuser basierend auf dem Bauplan
3. Instanz = das Haus in der Sonnenallee 22, Berlin
4. Interface = Vertrag über die zugesicherten Eigenschaften und Funktionen des Hauses

Was bedeutet für dich "mit Objekten zu arbeiten"?
Bist du "Daniel", der Architekt und beauftragt ist einen Bauplan für das Haus zu entwerfen?
Bist du "Bob", der Baumeister und baust nach dem Bauplan dann das Haus in der Sonnenallee 22?
Bist du "Karl", der Rechtsanwalt und erstellst den Vertrag?
Bist du "Anton", der Eigentümer des Hauses in der Sonnenallee 22, Berlin, der es nun schlüsselfertig übergeben bekommen hat?
Mit was willst du nun arbeiten? Mit dem Bauplan? Mit den Häusern? Oder mit dem Haus in der Sonnenallee 22, Berlin?
Wirst du dich beim "arbeiten" an den Vertrag halten?
Willst du als Anton eine Wand einreißen und so 2 Räume im Haus verbinden ohne einen Statiker zu beauftragen und ohne mit dem Daniel zu sprechen, geschweige denn auch mal den Karl um Rat zu fragen, ob du das darfst?
 
Zuletzt bearbeitet:
Rossibaer schrieb:
Eine Instanz-Property mit getter und setter im Interface als Default zu implementieren
Eine Instanzvariable eines Objekts nur mittels Interface setzen, mit/ohne Default ist egal.
Wenn die Werte irgendwo außerhalb liegen, widerspricht das ja der OO Idee.

Rossibaer schrieb:
Störst du dich an dem Inhalt des Interfaces IHaustier, wo tatsächlich ein privates statisches Feld vom Typ ConditionalWeakTable ist?
ja
Rossibaer schrieb:
2. Objekte = Häuser basierend auf dem Bauplan
3. Instanz = das Haus in der Sonnenallee 22, Berlin
objekt, instanz ist für mich synonym
 
Zuletzt bearbeitet:
Eine Instanzvariable eines Objekts nur mittels Interface setzen, mit/ohne Default ist egal.
Wenn die Werte irgendwo außerhalb liegen, widerspricht das ja der OO Idee.

Und eine Default-Implementierung im Interface ist für dich kein Widerspruch an sich?
Wie gesagt, das Interface ist eine Abstraktionssache, im Grunde eigentlich nur ein Vertrag, was deine Instanzen bereitstellen, wie die Implementierung dann dahinter ausschaut, ist ein Detail, was durch das Interface versteckt wird, denn heute holt sich z.B. deine Implementierung die Daten von einer Datenbank und morgen schon von einem Rest Service im Internet. Who knows? Ich wage mal die steile These, dass Interfaces zwar ein Sprachelement in C# sind, aber nicht zwingend ein notwendiger Bestandteil von OOP. Aber auch da lasse ich mich gern von anderen Entwicklern eines besseren belehren und verweise auch wieder nur darauf, dass es Sprachen gibt, die OOP sagen und ohne Interfaces auskommen.

objekt, instanz ist für mich synonym
da bin ich komplett anderer Meinung und sage, dass ein Objekt nicht mit einer Instanz gleichzustellen ist. Aber auch hier höre ich mir gern andere Meinungen und Argumente an und verweise auf das von mir genannte Beispiel mit dem Bauplan, Haus und einem Haus an einer speziellen Adresse.

Abschließend: Nach meinem jetzigen Kenntnisstand, wirst du das so nicht hinbekommen, was du vor hast, außer du verzichtest komplett auf Default-Implementierungen in den C# Interfaces, was in meinen Augen auch nichts weiter ist, als eine Aufweichung der bisherigen Konzepte in C#. Du machst dann natürlich deine Implementierung rein in den Klassen, da hast du komplett freie Hand, aber auch dann nicht die Möglichkeit eine Mehrfachvererbung zu imitieren, weil C# erstmal das Konzept fährt, dass es keine Mehrfachvererbung unterstützt und die Interface-Default-Implementierung aus meiner Sicht hier nur einen alternativen Weg darstellt, um ähnliches zu erreichen.

Oder kurz: Nö, geht nicht!
 
Micke schrieb:
Rossibaer schrieb:
Andererseits stellt sich mir die Frage, ob es wirklich notwendig ist Felder zu benutzen?
ja, und das geht über's Interface nicht (außer via static). Auch nicht über eine Property. Zum Ausprobieren kannst du versuchen mein Code Bsp. so umzubauen
wo habe ich behauptet etwas ginge über's Interface ?

Der Titel des Threads ist als rhetorisch markiert, und dennoch behaupte ich irgendwie zaubern zu können.
Und wie oft habe ich eigentlich mittlerweile eine Bedienungsanleitung für Interface bekommen - hat mal jemand mitgezählt ? Lohnt sich denn der ganze Aufwand, wenn ich es schon das 1. Mal nicht checke ?
Beitrag #26 hat Punkte angesprochen, die mir bei dem Thema wichtig scheinen, sodaß wir ruhig schliessen können.
 
Status
Für weitere Antworten geschlossen.
Zurück
Oben