Eines der Entwurfsziele von Frameworks wie Java und .NET besteht darin, zu ermöglichen, dass Code, der kompiliert wird, mit einer Version einer vorkompilierten Bibliothek funktioniert, auch mit nachfolgenden Versionen dieser Bibliothek gleich gut funktioniert, selbst wenn diese nachfolgenden Versionen neue Funktionen hinzufügen. Während das normale Paradigma in Sprachen wie C oder C ++ darin besteht, statisch verknüpfte ausführbare Dateien zu verteilen, die alle benötigten Bibliotheken enthalten, besteht das Paradigma in .NET und Java darin, Anwendungen als Sammlungen von Komponenten zu verteilen, die zur Laufzeit "verknüpft" sind .
Das COM-Modell, das .NET vorausging, versuchte, diesen allgemeinen Ansatz zu verwenden, hatte jedoch keine wirkliche Vererbung. Stattdessen definierte jede Klassendefinition effektiv sowohl eine Klasse als auch eine Schnittstelle mit demselben Namen, die alle öffentlichen Mitglieder enthielt. Instanzen waren vom Klassentyp, während Referenzen vom Schnittstellentyp waren. Das Deklarieren einer Klasse als von einer anderen abgeleitet, entsprach dem Deklarieren einer Klasse als Implementierung der Schnittstelle des anderen und erforderte, dass die neue Klasse alle öffentlichen Mitglieder der Klassen, von denen eine abgeleitet wurde, erneut implementiert. Wenn Y und Z von X und dann W von Y und Z abgeleitet sind, spielt es keine Rolle, ob Y und Z die Mitglieder von X unterschiedlich implementieren, da Z ihre Implementierungen nicht verwenden kann - es muss seine definieren besitzen. W kann Instanzen von Y und / oder Z einkapseln.
Die Schwierigkeit in Java und .NET besteht darin, dass Code Mitglieder erben darf und Zugriff darauf implizit auf die übergeordneten Mitglieder verweist. Angenommen, man hatte Klassen WZ wie oben:
class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z // Not actually permitted in C#
{
public static void Test()
{
var it = new W();
it.Foo();
}
}
Es scheint, als W.Test()
sollte das Erstellen einer Instanz von W die Implementierung der in Foo
definierten virtuellen Methode aufrufen X
. Angenommen, Y und Z befanden sich tatsächlich in einem separat kompilierten Modul, und obwohl sie beim Kompilieren von X und W wie oben definiert wurden, wurden sie später geändert und neu kompiliert:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
Was sollte nun der Effekt des Aufrufs sein, aber bis der Benutzer versuchte, W mit der neuen Version von Y und Z auszuführen, konnte kein Teil des Systems erkennen, dass es ein Problem gab (es sei denn, W wurde sogar als unzulässig angesehen vor den Änderungen an Y und Z).W.Test()
? Wenn das Programm vor der Verteilung statisch verknüpft werden musste, kann die statische Verknüpfungsphase möglicherweise erkennen, dass das Programm zwar vor dem Ändern von Y und Z keine Mehrdeutigkeit aufwies, die Änderungen an Y und Z jedoch zu Mehrdeutigkeiten geführt haben und der Linker dies ablehnen konnte Erstellen Sie das Programm, es sei denn oder bis eine solche Mehrdeutigkeit behoben ist. Andererseits ist es möglich, dass die Person, die sowohl W als auch die neuen Versionen von Y und Z hat, jemand ist, der das Programm einfach ausführen möchte und keinen Quellcode für irgendetwas davon hat. Beim W.Test()
Laufen wäre nicht mehr klar wasW.Test()