Я хотів би зібрати якомога більше інформації про версію API в .NET / CLR, а саме про те, як зміни API чи не порушують клієнтські програми. Спочатку визначимося з деякими термінами:
Зміна API - зміна загальнодоступного визначення типу, включаючи будь-якого з його публічних членів. Сюди входить зміна імен типу та членів, зміна базового типу типу, додавання / видалення інтерфейсів зі списку реалізованих інтерфейсів типу, додавання / видалення членів (включаючи перевантаження), зміна видимості членів, спосіб перейменування та параметрів типу, додавання значень за замовчуванням для параметрів методу, додавання / видалення атрибутів для типів та членів та додавання / видалення загальних параметрів типу для типів та членів (я щось пропустив?). Це не включає жодних змін в органах-членах або будь-яких змін у приватних членів (тобто ми не враховуємо відображення).
Перерва на бінарному рівні - зміна API, що призводить до того, що клієнтські збори, складені проти старшої версії API, потенційно не завантажуються новою версією. Приклад: зміна підпису методу, навіть якщо він дозволяє викликати так само, як і раніше (тобто: недійсність повернення значень типу / параметра за замовчуванням перевантажує).
Перерва на рівні джерела - зміна API, що призводить до того, що наявний код, записаний для компіляції проти старішої версії API, потенційно не відповідає новій версії. Однак вже складені клієнтські збори працюють, як і раніше. Приклад: додавання нової перевантаження, що може призвести до неоднозначності викликів методів, які були однозначними попередніми.
Зміна тихої семантики на рівні джерела - зміна API, що призводить до того, що існуючий код, записаний для компіляції проти старішої версії API, спокійно змінює його семантику, наприклад, викликаючи інший метод. Код, однак, повинен продовжувати компілювати без попереджень / помилок, і раніше складені збірки повинні працювати, як і раніше. Приклад: реалізація нового інтерфейсу для існуючого класу, що призводить до вибору іншого перевантаження під час вирішення перевантаження.
Кінцевою метою є каталогізація якомога більшої кількості безперебійних та спокійних змін семантики API та опис точного ефекту поломки, а також про те, які мови не впливають на них. Для розширення останнього: хоча деякі зміни впливають на всі мови універсально (наприклад, додавання нового члена в інтерфейс порушить реалізацію цього інтерфейсу на будь-якій мові), для отримання перерви для деяких потрібна дуже конкретна мовна семантика. Це, як правило, включає перевантаження методу, і взагалі все, що стосується перетворень неявного типу. Здається, тут немає жодного способу визначити "найменш загальний знаменник" навіть для мов, що відповідають вимогам CLS (тобто тих, що відповідають принаймні правилам "споживача CLS", визначеним у специфікації CLI) - хоча я " Я буду вдячний, якщо хтось виправить мене як неправий тут, тож це доведеться йти мовою за мовою. Найбільше цікавлять, звичайно, ті, хто поставляється з .NET з коробки: C #, VB і F #; але інші, такі як IronPython, IronRuby, Delphi Prism тощо, також є актуальними. Чим більше це кутовий випадок, тим цікавіше буде - такі речі, як видалення членів, досить очевидні, але тонкі взаємодії між, наприклад, перевантаження методу, необов'язкові параметри / параметри за замовчуванням, умови лямбда-виводу та оператори перетворення можуть бути дуже дивними. інколи.
Кілька прикладів для початку цього:
Додавання нового методу перевантажень
Вид: перерва на рівні джерела
Мови, на які впливає: C #, VB, F #
API перед зміною:
public class Foo
{
public void Bar(IEnumerable x);
}
API після зміни:
public class Foo
{
public void Bar(IEnumerable x);
public void Bar(ICloneable x);
}
Зразок коду клієнта, який працює перед зміною і розбивається після нього:
new Foo().Bar(new int[0]);
Додавання нових неявних операторів перетворення перевантажує
Вид: перерва на рівні джерела.
Мови, на які впливає: C #, VB
Мови не зачіпаються: F #
API перед зміною:
public class Foo
{
public static implicit operator int ();
}
API після зміни:
public class Foo
{
public static implicit operator int ();
public static implicit operator float ();
}
Зразок коду клієнта, який працює перед зміною і розбивається після нього:
void Bar(int x);
void Bar(float x);
Bar(new Foo());
Примітки: F # не порушена, тому що не має жодної підтримки мовного рівня для перевантажених операторів, ні явної, ні явної - обидва повинні бути викликані безпосередньо як op_Explicit
і op_Implicit
методи.
Додавання нових методів примірника
Вид: тиха семантика на рівні джерела.
Мови, на які впливає: C #, VB
Мови не зачіпаються: F #
API перед зміною:
public class Foo
{
}
API після зміни:
public class Foo
{
public void Bar();
}
Приклад клієнтського коду, який зазнає тихої зміни семантики:
public static class FooExtensions
{
public void Bar(this Foo foo);
}
new Foo().Bar();
Примітки: F # не порушена, оскільки не підтримує рівень мови для ExtensionMethodAttribute
вимагає, щоб методи розширення CLS називалися статичними методами.