Ich möchte so viele Informationen wie möglich über die API-Versionierung in .NET / CLR sammeln und insbesondere darüber, wie API-Änderungen Clientanwendungen beschädigen oder nicht. Definieren wir zunächst einige Begriffe:
API-Änderung - Eine Änderung der öffentlich sichtbaren Definition eines Typs, einschließlich eines seiner öffentlichen Mitglieder. Dies umfasst das Ändern von Typ- und Mitgliedsnamen, das Ändern des Basistyps eines Typs, das Hinzufügen / Entfernen von Schnittstellen aus der Liste der implementierten Schnittstellen eines Typs, das Hinzufügen / Entfernen von Mitgliedern (einschließlich Überladungen), das Ändern der Sichtbarkeit von Mitgliedern, das Umbenennen von Methoden- und Typparametern sowie das Hinzufügen von Standardwerten für Methodenparameter, Hinzufügen / Entfernen von Attributen für Typen und Elemente und Hinzufügen / Entfernen von generischen Typparametern für Typen und Elemente (habe ich etwas verpasst?). Dies schließt keine Änderungen in den Mitgliedsorganen oder Änderungen an privaten Mitgliedern ein (dh wir berücksichtigen die Reflexion nicht).
Unterbrechung auf Binärebene - Eine API-Änderung, die dazu führt, dass Client-Assemblys, die mit einer älteren Version der API kompiliert wurden, möglicherweise nicht mit der neuen Version geladen werden. Beispiel: Ändern der Methodensignatur, auch wenn sie auf die gleiche Weise wie zuvor aufgerufen werden kann (dh: void, um Überladungen von Typ- / Parameter-Standardwerten zurückzugeben).
Unterbrechung auf Quellenebene - Eine API-Änderung, die dazu führt, dass vorhandener Code zum Kompilieren mit einer älteren Version der API geschrieben wird, die möglicherweise nicht mit der neuen Version kompiliert werden kann. Bereits kompilierte Client-Assemblys funktionieren jedoch wie zuvor. Beispiel: Hinzufügen einer neuen Überladung, die zu Mehrdeutigkeiten bei Methodenaufrufen führen kann, die zuvor eindeutig waren.
Leise Semantikänderung auf Quellenebene - Eine API-Änderung, die dazu führt, dass vorhandener Code, der zum Kompilieren mit einer älteren Version der API geschrieben wurde, seine Semantik leise ändert, z. B. durch Aufrufen einer anderen Methode. Der Code sollte jedoch weiterhin ohne Warnungen / Fehler kompiliert werden, und zuvor kompilierte Assemblys sollten wie zuvor funktionieren. Beispiel: Implementieren einer neuen Schnittstelle in einer vorhandenen Klasse, die dazu führt, dass während der Überlastungsauflösung eine andere Überlastung ausgewählt wird.
Das ultimative Ziel ist es, so viele Änderungen der Semantik-API wie möglich zu katalogisieren und die genauen Auswirkungen von Unterbrechungen zu beschreiben und zu beschreiben, welche Sprachen davon betroffen sind und welche nicht. Um letzteres zu erweitern: Während einige Änderungen alle Sprachen allgemein betreffen (z. B. das Hinzufügen eines neuen Mitglieds zu einer Schnittstelle führt zu einer Unterbrechung der Implementierung dieser Schnittstelle in einer beliebigen Sprache), erfordern einige eine sehr spezifische Sprachsemantik, um eine Unterbrechung zu erreichen. Dies beinhaltet in der Regel eine Methodenüberladung und im Allgemeinen alles, was mit impliziten Typkonvertierungen zu tun hat. Es scheint hier keine Möglichkeit zu geben, den "kleinsten gemeinsamen Nenner" zu definieren, selbst für CLS-konforme Sprachen (dh solche, die mindestens den in der CLI-Spezifikation definierten Regeln des "CLS-Verbrauchers" entsprechen) - obwohl ich ' Ich würde es begrüßen, wenn mich jemand hier als falsch korrigiert - also muss dies Sprache für Sprache gehen. Am interessantesten sind natürlich diejenigen, die standardmäßig mit .NET geliefert werden: C #, VB und F #; Aber auch andere wie IronPython, IronRuby, Delphi Prism usw. sind relevant. Je mehr es sich um einen Eckfall handelt, desto interessanter wird es sein - Dinge wie das Entfernen von Mitgliedern sind ziemlich selbstverständlich, aber subtile Wechselwirkungen zwischen z. B. Methodenüberladung, optionalen / Standardparametern, Lambda-Typ-Inferenz und Konvertierungsoperatoren können sehr überraschend sein manchmal.
Einige Beispiele, um dies zu starten:
Hinzufügen neuer Methodenüberladungen
Art: Unterbrechung auf Quellenebene
Betroffene Sprachen: C #, VB, F #
API vor Änderung:
public class Foo
{
public void Bar(IEnumerable x);
}
API nach Änderung:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
Beispiel für einen Clientcode, der vor der Änderung funktioniert und danach beschädigt wird:
new Foo().Bar(new int[0]);
Hinzufügen neuer Überladungen von impliziten Konvertierungsoperatoren
Art: Unterbrechung auf Quellenebene.
Betroffene Sprachen: C #, VB
Nicht betroffene Sprachen: F #
API vor Änderung:
public class Foo
{
public static implicit operator int ();
}
API nach Änderung:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
Beispiel für einen Clientcode, der vor der Änderung funktioniert und danach beschädigt wird:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
Anmerkungen: F # ist nicht fehlerhaft, da es keine Unterstützung auf Sprachebene für überladene Operatoren bietet, weder explizit noch implizit - beide müssen direkt als op_Explicit
und op_Implicit
Methoden aufgerufen werden.
Hinzufügen neuer Instanzmethoden
Art: Änderung der stillen Semantik auf Quellenebene.
Betroffene Sprachen: C #, VB
Nicht betroffene Sprachen: F #
API vor Änderung:
public class Foo
{
}
API nach Änderung:
public class Foo
{
public void Bar();
}
Beispiel für einen Clientcode, der eine stille Änderung der Semantik erfährt:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
Anmerkungen: F # ist nicht fehlerhaft, da es keine Unterstützung auf Sprachebene ExtensionMethodAttribute
bietet und erfordert, dass CLS-Erweiterungsmethoden als statische Methoden aufgerufen werden.