Je voudrais rassembler autant d'informations que possible concernant le contrôle de version d'API dans .NET / CLR, et en particulier comment les changements d'API font ou ne cassent pas les applications clientes. Définissons d'abord quelques termes:
Modification de l'API - un changement dans la définition publiquement visible d'un type, y compris l'un de ses membres publics. Cela inclut la modification du type et des noms de membre, la modification du type de base d'un type, l'ajout / la suppression d'interfaces de la liste des interfaces implémentées d'un type, l'ajout / la suppression de membres (y compris les surcharges), la modification de la visibilité des membres, la modification du nom de la méthode et des paramètres de type, l'ajout de valeurs par défaut pour les paramètres de méthode, ajouter / supprimer des attributs sur les types et les membres, et ajouter / supprimer des paramètres de type génériques sur les types et les membres (ai-je oublié quelque chose?). Cela n'inclut aucun changement dans les comités membres, ni aucun changement dans les membres privés (c'est-à-dire que nous ne prenons pas en compte la réflexion).
Interruption au niveau binaire - une modification de l'API qui se traduit par des assemblys clients compilés avec une ancienne version de l'API ne pouvant pas se charger avec la nouvelle version. Exemple: modification de la signature d'une méthode, même si elle permet d'être appelée de la même manière que précédemment (ie: void pour renvoyer des surcharges de valeurs par défaut de type / paramètre).
Saut au niveau de la source - un changement d'API qui se traduit par un code existant écrit pour être compilé avec une ancienne version de l'API qui ne peut pas être compilée avec la nouvelle version. Cependant, les assemblys clients déjà compilés fonctionnent comme auparavant. Exemple: ajouter une nouvelle surcharge qui peut entraîner une ambiguïté dans les appels de méthode qui étaient sans ambiguïté auparavant.
Modification de la sémantique silencieuse au niveau de la source - une modification de l'API qui se traduit par un code existant écrit pour être compilé avec une version plus ancienne de l'API, modifie discrètement sa sémantique, par exemple en appelant une méthode différente. Le code doit cependant continuer à compiler sans avertissements / erreurs, et les assemblys précédemment compilés devraient fonctionner comme avant. Exemple: implémentation d'une nouvelle interface sur une classe existante qui se traduit par une surcharge différente choisie lors de la résolution de surcharge.
Le but ultime est de cataloguer autant de changements d'API de sémantique de rupture et de silence que possible, et de décrire l'effet exact de la rupture, et les langues qui sont et ne sont pas affectées par celui-ci. Pour développer cette dernière: alors que certains changements affectent universellement toutes les langues (par exemple, l'ajout d'un nouveau membre à une interface interrompra les implémentations de cette interface dans n'importe quelle langue), certains nécessitent une sémantique de langage très spécifique pour entrer en jeu pour faire une pause. Cela implique généralement une surcharge de méthode et, en général, tout ce qui a à voir avec les conversions de types implicites. Il ne semble pas y avoir de moyen de définir le "dénominateur le moins commun" ici même pour les langages conformes CLS (c'est-à-dire ceux qui se conforment au moins aux règles du "consommateur CLS" telles que définies dans les spécifications CLI) - bien que je ' J'apprécierai si quelqu'un me corrige comme se trompant ici - donc cela devra aller langue par langue. Ceux qui sont les plus intéressants sont naturellement ceux fournis avec .NET dès la sortie de la boîte: C #, VB et F #; mais d'autres, comme IronPython, IronRuby, Delphi Prism, etc. sont également pertinents. Plus il s'agit d'un cas d'angle, plus il sera intéressant - des choses comme la suppression de membres sont assez évidentes, mais des interactions subtiles entre, par exemple, la surcharge de méthode, les paramètres facultatifs / par défaut, l'inférence de type lambda et les opérateurs de conversion peuvent être très surprenantes a l'heure.
Quelques exemples pour démarrer ceci:
Ajout de nouvelles surcharges de méthode
Type: coupure au niveau de la source
Langues affectées: C #, VB, F #
API avant changement:
public class Foo
{
public void Bar(IEnumerable x);
}
API après modification:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
Exemple de code client fonctionnant avant le changement et cassé après:
new Foo().Bar(new int[0]);
Ajout de nouvelles surcharges d'opérateurs de conversion implicites
Type: coupure au niveau de la source.
Langues affectées: C #, VB
Langues non concernées: F #
API avant changement:
public class Foo
{
public static implicit operator int ();
}
API après modification:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
Exemple de code client fonctionnant avant le changement et cassé après:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
Remarques: F # n'est pas rompu, car il ne prend pas en charge le niveau de langue pour les opérateurs surchargés, ni explicites ni implicites - les deux doivent être appelés directement en tant que op_Explicit
et op_Implicit
méthodes.
Ajout de nouvelles méthodes d'instance
Type: modification de la sémantique silencieuse au niveau de la source.
Langues affectées: C #, VB
Langues non concernées: F #
API avant changement:
public class Foo
{
}
API après modification:
public class Foo
{
public void Bar();
}
Exemple de code client qui subit une modification sémantique discrète:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
Remarques: F # n'est pas rompu, car il ne prend pas en charge le niveau de langue ExtensionMethodAttribute
et nécessite que les méthodes d'extension CLS soient appelées comme méthodes statiques.