Vorrei raccogliere quante più informazioni possibili sul controllo delle versioni delle API in .NET / CLR e in particolare su come le modifiche alle API fanno o non rompono le applicazioni client. Innanzitutto, definiamo alcuni termini:
Modifica API : una modifica della definizione pubblicamente visibile di un tipo, inclusi i suoi membri pubblici. Ciò include la modifica del tipo e dei nomi dei membri, la modifica del tipo di base di un tipo, l'aggiunta / rimozione di interfacce dall'elenco delle interfacce implementate di un tipo, l'aggiunta / rimozione di membri (compresi i sovraccarichi), la modifica della visibilità dei membri, la ridenominazione del metodo e dei parametri del tipo, l'aggiunta di valori predefiniti per i parametri del metodo, l'aggiunta / rimozione di attributi su tipi e membri e l'aggiunta / rimozione di parametri di tipo generico su tipi e membri (mi sono perso qualcosa?). Ciò non include eventuali modifiche negli organi membri o eventuali modifiche ai membri privati (ovvero non prendiamo in considerazione Reflection).
Interruzione a livello binario : una modifica dell'API che determina l'assemblaggio di assembly client rispetto alla versione precedente dell'API che potenzialmente non si carica con la nuova versione. Esempio: modifica della firma del metodo, anche se consente di essere richiamato come prima (ovvero: void per restituire sovraccarichi di valori predefiniti di tipo / parametro).
Interruzione a livello di origine : una modifica dell'API che comporta la scrittura del codice esistente per la compilazione con la versione precedente dell'API che potenzialmente non si sta compilando con la nuova versione. Tuttavia, gli assembly client già compilati funzionano come prima. Esempio: aggiunta di un nuovo sovraccarico che può provocare ambiguità nelle chiamate di metodo precedenti non ambigue.
Modifica della semantica silenziosa a livello di origine : una modifica dell'API che si traduce in codice esistente scritto per la compilazione con una versione precedente dell'API modifica silenziosamente la sua semantica, ad esempio chiamando un metodo diverso. Tuttavia, il codice dovrebbe continuare a essere compilato senza avvisi / errori e gli assembly precedentemente compilati dovrebbero funzionare come prima. Esempio: implementazione di una nuova interfaccia su una classe esistente che determina la scelta di un sovraccarico diverso durante la risoluzione del sovraccarico.
L'obiettivo finale è quello di catalogare il maggior numero possibile di modifiche API semantiche di interruzione e di quiete e descrivere l'effetto esatto della rottura e quali lingue sono e non sono interessate da essa. Espandersi su quest'ultimo: mentre alcune modifiche influenzano universalmente tutte le lingue (ad esempio l'aggiunta di un nuovo membro a un'interfaccia interromperà le implementazioni di tale interfaccia in qualsiasi lingua), alcune richiedono una semantica linguistica molto specifica per entrare in gioco per ottenere una pausa. Questo in genere comporta un sovraccarico del metodo e, in generale, qualsiasi cosa abbia a che fare con conversioni di tipo implicite. Non sembra esserci alcun modo per definire qui il "minimo comune denominatore" anche per i linguaggi conformi a CLS (ovvero quelli conformi almeno alle regole del "consumatore CLS" come definito nelle specifiche CLI) - sebbene io ' Apprezzerò se qualcuno mi corregge come sbagliato qui - quindi questo dovrà andare lingua per lingua. Quelli di maggior interesse sono naturalmente quelli forniti con .NET out of the box: C #, VB e F #; ma anche altri, come IronPython, IronRuby, Delphi Prism ecc. sono rilevanti. Più è un caso angolare, più interessante sarà - cose come la rimozione dei membri sono abbastanza evidenti, ma le interazioni sottili tra ad esempio il sovraccarico del metodo, i parametri opzionali / predefiniti, l'inferenza del tipo lambda e gli operatori di conversione possono essere molto sorprendenti a volte.
Alcuni esempi per iniziare questo:
Aggiunta di nuovi sovraccarichi di metodo
Tipo: interruzione a livello di sorgente
Lingue interessate: C #, VB, F #
API prima della modifica:
public class Foo
{
public void Bar(IEnumerable x);
}
API dopo la modifica:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
Codice client di esempio funzionante prima della modifica e interrotto dopo:
new Foo().Bar(new int[0]);
Aggiunta di nuovi sovraccarichi dell'operatore di conversione implicita
Tipo: interruzione a livello di sorgente.
Lingue interessate: C #, VB
Lingue non interessate: F #
API prima della modifica:
public class Foo
{
public static implicit operator int ();
}
API dopo la modifica:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
Codice client di esempio funzionante prima della modifica e interrotto dopo:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
Note: F # non viene interrotto, perché non ha alcun supporto a livello di lingua per operatori sovraccarichi, né espliciti né impliciti - entrambi devono essere chiamati direttamente come op_Explicit
e op_Implicit
metodi.
Aggiunta di nuovi metodi di istanza
Tipo: cambiamento di semantica silenziosa a livello di sorgente.
Lingue interessate: C #, VB
Lingue non interessate: F #
API prima della modifica:
public class Foo
{
}
API dopo la modifica:
public class Foo
{
public void Bar();
}
Codice client di esempio che subisce una modifica semantica silenziosa:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
Note: F # non viene interrotto, perché non ha supporto a livello di lingua per ExtensionMethodAttribute
e richiede che i metodi di estensione CLS siano chiamati come metodi statici.