Остаточний посібник з розбиття API в .NET


227

Я хотів би зібрати якомога більше інформації про версію 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 називалися статичними методами.


Напевно, Microsoft вже охоплює це ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Роберт Харві

1
@Robert: у вашому посиланні йдеться про щось дуже інше - воно описує конкретні неполадки в самій .NET Framework . Це більш широке запитання, яке описує загальні шаблони, які можуть внести переломні зміни у ваші власні API (як автор бібліотеки / рамки). Я не знаю жодного такого документа від MS, який був би повним, хоча будь-які посилання на такі, навіть якщо вони неповні, безумовно вітаються.
Павло Мінаєв

У будь-якій з цих категорій "перерви" чи існує така, в якій проблема стане очевидною лише під час виконання?
Рохіт

1
Так, категорія "бінарний перерва". У цьому випадку у вас вже є стороннє зібрання, складене проти всіх версій вашої збірки. Якщо ви скинете нову версію своєї збірки на місці, стороння збірка перестає працювати - вона просто не завантажується під час виконання, або працює неправильно.
Павло Мінаєв

3
Я додав би їх у допис та коментарі blogs.msdn.com/b/ericlippert/archive/2012/01/09/…
Лукаш

Відповіді:


42

Зміна підпису методу

Вид: Перерва на двійковому рівні

Мови, на які впливає: C # (найімовірніше, VB та F #, але не перевірено)

API перед зміною

public static class Foo
{
    public static void bar(int i);
}

API після зміни

public static class Foo
{
    public static bool bar(int i);
}

Зразковий код клієнта, який працює перед зміною

Foo.bar(13);

15
Насправді це може бути і перерва на рівні джерела, якщо хтось намагається створити делегата для bar.
Павло Мінаєв

Це теж правда. Цю особливу проблему я виявив, коли вносив деякі зміни до друкарських програм у програмі своїх компаній. Коли оновлення було випущено, не всі DLL, на які посилаються ці утиліти, були перекомпільовані та випущені, щоб викинути метод не знайдений виняток.
Джастін Друрі

1
Це повертається до того, що типи повернення не враховуються для підпису методу. Ви також не можете перевантажувати дві функції, засновані виключно на типі повернення. Та ж проблема.
Jason Short

1
підвідвід на цю відповідь: чи знає хто-небудь про зміну додавання значення dotnet4 за замовчуванням «public static void void (int i = 0);» чи змінити значення за замовчуванням з одного значення на інше?
k3b

1
Для тих, хто збирається зайти на цю сторінку, я думаю, що для C # (і "я думаю" більшість інших мов OOP), типи повернення не сприяють підпису методу. Так, відповідь правильна, що зміни Підпису сприяють зміні рівня Двійкового. АЛЕ приклад не здається правильним IMHO правильний приклад , який я можу думати про ДО громадського десяткового Sum (Int А, внутр б) Після громадського десяткового Sum (десяткове в десятковому б) Будь ласка , зверніться за посиланням MSDN 3.6 Підписи і перевантаження
Bhanu Chhabra

40

Додавання параметра зі значенням за замовчуванням.

Вид перерви: Перерва на двійковому рівні

Навіть якщо вихідний код виклику не потрібно змінювати, його все одно потрібно перекомпілювати (як і при додаванні звичайного параметра).

Це тому, що C # збирає значення за замовчуванням параметрів безпосередньо в виклику. Це означає, що якщо ви не перекомпілюєте, ви отримаєте MissingMethodException, оскільки стара збірка намагається викликати метод з меншими аргументами.

API перед зміною

public void Foo(int a) { }

API після змін

public void Foo(int a, string b = null) { }

Зразок коду клієнта, який згодом порушується

Foo(5);

Клієнтський код потрібно перекомпілювати на Foo(5, null)рівні байт-коду. Викликана збірка міститиме лише Foo(int, string), але не Foo(int). Це тому, що значення параметрів за замовчуванням є суто мовною особливістю, час виконання .Net нічого про них не знає. (Це також пояснює, чому значення за замовчуванням повинні бути константами часу компіляції в C #).


2
це Func<int> f = Foo;
краща

26

Це було дуже не очевидно, коли я виявив це, особливо зважаючи на різницю з однаковою ситуацією для інтерфейсів. Це зовсім не перерва, але досить дивно, що я вирішив включити її:

Рефакторинг членів класу на базовий клас

Вид: не перерва!

Мови, які впливають: жодна (тобто жодна не порушена)

API перед зміною:

class Foo
{
    public virtual void Bar() {}
    public virtual void Baz() {}
}

API після зміни:

class FooBase
{
    public virtual void Bar() {}
}

class Foo : FooBase
{
    public virtual void Baz() {}
}

Приклад коду, який продовжує працювати протягом усієї зміни (навіть якщо я очікував, що вона порушиться):

// C++/CLI
ref class Derived : Foo
{
   public virtual void Baz() {{

   // Explicit override    
   public virtual void BarOverride() = Foo::Bar {}
};

Примітки:

C ++ / CLI - єдина .NET мова, яка має конструкцію, аналогічну явній реалізації інтерфейсу для членів віртуального базового класу, - "явне переопрацювання". Я повністю очікував, що це призведе до того ж виду поломки, що і при переміщенні членів інтерфейсу до базового інтерфейсу (оскільки ІЛЕ, генерований для явного переопределення, такий же, як і для явної реалізації). На мій подив, це не так - навіть незважаючи на те, що згенерований IL все ще вказує на те, що BarOverrideзамінює, Foo::Barа не FooBase::Bar, збірний навантажувач досить розумний, щоб замінити один на інший правильно, без будь-яких скарг - мабуть, факт, що Fooє класом, є важливим. Піди розберися...


3
Поки базовий клас знаходиться в одній збірці. Інакше це бінарна зміна.
Джеремі

@Jeremy, який код порушується в такому випадку? Чи порушить будь-яке використання зовнішнього абонента Baz () або це лише проблема з людьми, які намагаються розширити Foo та змінити Baz ()?
ChaseMedallion

@ChaseMedallion це зламано, якщо ви користувач секонд-хенду. Наприклад, компільована DLL посилається на старішу версію Foo, і ви посилаєтесь на цю компільовану DLL, але також використовуєте більш нову версію DLL Foo. Це зривається дивною помилкою, або, принаймні, це робиться для мене в бібліотеках, які я раніше розвивав.
Джеремі

19

Цей - це, мабуть, не дуже очевидний особливий випадок "додавання / видалення членів інтерфейсу", і я подумав, що він заслуговує на власний запис у світлі іншого випадку, який я збираюся опублікувати наступним. Так:

Члени інтерфейсу рефакторингу переходять у базовий інтерфейс

Вид: перерви на вихідному та бінарному рівнях

Мови, на які впливає: C #, VB, C ++ / CLI, F # (для перерви у джерелі; двійковий звичайно впливає на будь-яку мову)

API перед зміною:

interface IFoo
{
    void Bar();
    void Baz();
}

API після зміни:

interface IFooBase 
{
    void Bar();
}

interface IFoo : IFooBase
{
    void Baz();
}

Зразок коду клієнта, який порушується зміною на рівні джерела:

class Foo : IFoo
{
   void IFoo.Bar() { ... }
   void IFoo.Baz() { ... }
}

Зразок коду клієнта, який порушується зміною на бінарному рівні;

(new Foo()).Bar();

Примітки:

Для розриву рівня джерела проблема полягає в тому, що всі C #, VB і C ++ / CLI вимагають точного імені інтерфейсу в декларації про реалізацію члена інтерфейсу; таким чином, якщо член переміщується до базового інтерфейсу, код більше не буде компілюватися.

Бінарний розрив пов'язаний з тим, що методи інтерфейсу повністю кваліфіковані в створеному ІЛ для явних реалізацій, а ім'я інтерфейсу також має бути точним.

Неявна реалізація, коли вона доступна (наприклад, C # і C ++ / CLI, але не VB), буде добре працювати як на вихідному, так і на бінарному рівні. Виклики методів також не порушуються.


Це не вірно для всіх мов. Для VB це не є кращою зміною вихідного коду. Для C # вона є.
Джеремі

Так Implements IFoo.Barбуде прозоро посилатися IFooBase.Bar?
Павло Мінаєв

Так, це насправді, ви можете посилатися на члена безпосередньо або опосередковано через спадковий інтерфейс, коли ви його реалізуєте. Однак це завжди є бінарною зміною.
Джеремі

15

Упорядкування перелічених значень

Вид перерви: зміна рівня джерела / типова семантика на рівні бінарного рівня

Мови, на які впливає: усі

При упорядкуванні перерахованих значень збережеться сумісність на рівні джерела, оскільки літерали мають однакову назву, але їх порядкові індекси будуть оновлені, що може спричинити деякі тихі перерви на рівні джерела.

Ще гірше - безшумні перерви бінарного рівня, які можна ввести, якщо код клієнта не буде перекомпільований проти нової версії API. Значення Enum є константами часу компіляції і тому будь-яке їх використання виводиться в ІЛ клієнтської збірки. Цей випадок часом може бути особливо важким.

API перед зміною

public enum Foo
{
   Bar,
   Baz
}

API після змін

public enum Foo
{
   Baz,
   Bar
}

Зразок коду клієнта, який працює, але згодом порушується:

Foo.Bar < Foo.Baz

12

Це дійсно дуже рідкісна річ на практиці, але, тим не менш, дивна, коли це відбувається.

Додавання нових неповантажених членів

Вид: перерва рівня джерела або тиха зміна семантики.

Мови, на які впливає: C #, VB

Мови не зачіпаються: F #, C ++ / CLI

API перед зміною:

public class Foo
{
}

API після зміни:

public class Foo
{
    public void Frob() {}
}

Зразок коду клієнта, який порушується зміною:

class Bar
{
    public void Frob() {}
}

class Program
{
    static void Qux(Action<Foo> a)
    {
    }

    static void Qux(Action<Bar> a)
    {
    }

    static void Main()
    {
        Qux(x => x.Frob());        
    }
}

Примітки:

Проблема тут викликана виведенням лямбда-типу в C # і VB за наявності роздільної здатності перевантаження. Тут обмежена форма набору качок використовується для розриву зв'язків, де збігається більше одного типу, перевіряючи, чи має тіло лямбда сенс для даного типу - якщо лише один тип призводить до складання тіла, яке вибирається.

Небезпека тут полягає в тому, що клієнтський код може мати перевантажену групу методів, де одні методи приймають аргументи його власних типів, а інші приймають аргументи типів, викритих вашою бібліотекою. Якщо будь-який його код потім спирається на алгоритм виводу типу, щоб визначити правильний метод, заснований виключно на присутності або відсутності членів, то додавання нового члена до одного з ваших типів з тим же ім'ям, як і в одному з типів клієнта, може потенційно викидати умовивід вимкнено, що призводить до неоднозначності під час вирішення перевантаження.

Зауважте, що типи Fooта Barв цьому прикладі жодним чином не пов’язані між собою, ні успадкуванням, ні іншим чином. Для їх запуску достатньо лише використання їх в одній групі методів, і якщо це відбувається в коді клієнта, ви не маєте над цим контролю.

Приклад коду вище демонструє більш просту ситуацію, коли це перерва на рівні джерела (тобто результати помилок компілятора). Однак це також може бути тиха зміна семантики, якщо перевантаження, яке було вибрано за допомогою висновку, мало інші аргументи, які в іншому випадку призвели б до її ранжирування нижче (наприклад, необов'язкові аргументи зі значеннями за замовчуванням або тип невідповідності між заявленим та фактичним аргументом, що вимагає неявного конверсія). У такому сценарії роздільна здатність перевантаження більше не вийде з ладу, але компілятор буде спокійно вибирати інше перевантаження. На практиці, однак, дуже важко зіткнутися з цією справою без ретельної побудови підписів методів, щоб навмисно викликати це.


9

Перетворіть неявну реалізацію інтерфейсу в явну.

Вид перерви: джерело та двійкове

Мови, на які впливає: Усі

Це насправді лише варіація зміни доступності методу - його лише трохи тонкіше, оскільки легко не помітити той факт, що не весь доступ до методів інтерфейсу обов'язково здійснюється через посилання на тип інтерфейсу.

API перед зміною:

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator();
}

API після змін:

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator();
}

Зразок коду клієнта, який працює до зміни і після цього порушується:

new Foo().GetEnumerator(); // fails because GetEnumerator() is no longer public

7

Перетворіть явну реалізацію інтерфейсу в неявну.

Вид перерви: Джерело

Мови, на які впливає: Усі

Рефакторинг явної реалізації інтерфейсу до неявної є більш тонким у тому, як він може зламати API. На перший погляд, здається, що це має бути відносно безпечним, однак у поєднанні із спадщиною це може спричинити проблеми.

API перед зміною:

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() { yield return "Foo"; }
}

API після змін:

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator() { yield return "Foo"; }
}

Зразок коду клієнта, який працює до зміни і після цього порушується:

class Bar : Foo, IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() // silently hides base instance
    { yield return "Bar"; }
}

foreach( var x in new Bar() )
    Console.WriteLine(x);    // originally output "Bar", now outputs "Foo"

Вибачте, я не дуже дотримуюсь - напевно, зразок коду перед зміною API взагалі не збирався б, оскільки до зміни Fooне було відкритого методу GetEnumerator, і ви викликаєте метод через посилання типу Foo. .
Павло Мінаєв

Дійсно, я спробував спростити приклад із пам’яті, і це закінчилося «foobar» (вибачте за каламбур). Я оновив приклад, щоб правильно продемонструвати випадок (і бути складеним).
Л.Бушкін

У моєму прикладі проблема викликана більш ніж просто переходом методу інтерфейсу від неявного до публічного. Це залежить від способу, який компілятор C # визначає, який метод викликати в циклі foreach. Враховуючи правила роздільної здатності компілятора ses, він переходить від версії у похідному класі до версії в базовому класі.
Л.Бушкін

Ви забули yield return "Bar":) але так, я бачу, куди це йде зараз - foreachзавжди називає публічний метод названим GetEnumerator, навіть якщо це не реальна реалізація IEnumerable.GetEnumerator. Здається, це має ще один кут: навіть якщо у вас є лише один клас, і він реалізується IEnumerableявно, це означає, що це зміна джерела для додавання до нього публічного методу GetEnumerator, оскільки тепер foreachбуде використовувати цей метод над реалізацією інтерфейсу. Також ця ж проблема стосується і IEnumeratorвпровадження ...
Павло Мінаєв

6

Зміна поля на властивість

Вид перерви: API

Зачеплені мови: Visual Basic та C # *

Інформація: Коли ви змінюєте нормальне поле або змінну у властивість у візуальній базі, будь-який зовнішній код, який посилається на цей член, будь-яким чином потрібно буде перекомпілювати.

API перед зміною:

Public Class Foo    
    Public Shared Bar As String = ""    
End Class

API після змін:

Public Class Foo
    Private Shared _Bar As String = ""
    Public Shared Property Bar As String
        Get
            Return _Bar
        End Get
        Set(value As String)
            _Bar = value
        End Set
    End Property
End Class    

Зразок коду клієнта, який працює, але згодом порушується:

Foo.Bar = "foobar"

2
Насправді це також розіб'є речі в C #, тому що властивості outта refаргументи методів не можна використовувати , на відміну від полів, і не можуть бути ціллю унарного &оператора.
Павло Мінаєв

5

Додавання простору імен

Перерва на рівні джерела / тиха семантика на рівні джерела

Завдяки тому, як працює дозвіл простору імен у vb.Net, додавання простору імен до бібліотеки може спричинити, що код Visual Basic, зібраний з попередньою версією API, не компілюється з новою версією.

Приклад клієнтського коду:

Imports System
Imports Api.SomeNamespace

Public Class Foo
    Public Sub Bar()
        Dim dr As Data.DataRow
    End Sub
End Class

Якщо нова версія API додає простір імен Api.SomeNamespace.Data, то наведений вище код не буде компілюватися.

Це ускладнюється з імпортом простору імен на рівні проекту. Якщо Imports Systemз вищевказаного коду пропущено, але ім’я Systemімпортується на рівні проекту, то код все-таки може призвести до помилки.

Однак якщо Api включає клас DataRowу своєму Api.SomeNamespace.Dataпросторі імен, то код буде компілюватися, але drвін буде екземпляром, System.Data.DataRowколи компілюється зі старою версією API та Api.SomeNamespace.Data.DataRowколи компілюється з новою версією API.

Перейменування аргументу

Перерва на рівні джерела

Зміна імен аргументів - це суттєва зміна vb.net від версії 7 (?) (.Net версії 1?) Та c # .net від версії 4 (.Net версія 4).

API перед зміною:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

API після зміни:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string y) {
           ...
        }
    }
}

Приклад клієнтського коду:

Api.SomeNamespace.Foo.Bar(x:"hi"); //C#
Api.SomeNamespace.Foo.Bar(x:="hi") 'VB

Параметри посилання

Перерва на рівні джерела

Додавання методу, що замінює той самий підпис, за винятком того, що один параметр передається посиланням замість значення, призведе до того, що джерело vb, що посилається на API, не зможе вирішити функцію. Visual Basic не має можливості (?) Диференціювати ці методи в точці виклику, якщо вони не мають різних назв аргументів, тому така зміна може спричинити непридатність обох членів від vb-коду.

API перед зміною:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

API після зміни:

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
        public static void Bar(ref string x) {
           ...
        }
    }
}

Приклад клієнтського коду:

Api.SomeNamespace.Foo.Bar(str)

Поле до зміни власності

Перерва на рівні бінарного рівня / перерва на рівні джерела

Крім очевидного розриву бінарного рівня, це може спричинити перерву на рівні джерела, якщо член перейде до методу за посиланням.

API перед зміною:

namespace SomeNamespace {
    public class Foo {
        public int Bar;
    }
}

API після зміни:

namespace SomeNamespace {
    public class Foo {
        public int Bar { get; set; }
    }
}

Приклад клієнтського коду:

FooBar(ref Api.SomeNamespace.Foo.Bar);

4

Зміна API:

  1. Додавання атрибута [Insolete] (ви якось охоплювали це атрибутами згадування; однак це може бути переломною зміною при використанні попередження як помилки.)

Перерва на двійковому рівні:

  1. Переміщення типу з однієї збірки в іншу
  2. Зміна простору імен типу
  3. Додавання типу базового класу з іншої збірки.
  4. Додавання нового члена (захищеного від події), який використовує тип з іншої збірки (Class2) як обмеження аргументу шаблону.

    protected void Something<T>() where T : Class2 { }
  5. Зміна дочірнього класу (Class3) на похід від типу в іншій збірці, коли клас використовується як аргумент шаблону для цього класу.

    protected class Class3 : Class2 { }
    protected void Something<T>() where T : Class3 { }

Зміна тихої семантики на рівні джерела:

  1. Додавання / видалення / змінення переопределень рівних (), GetHashCode () або ToString ()

(не впевнений, де вони підходять)

Зміни розгортання:

  1. Додавання / усунення залежностей / посилань
  2. Оновлення залежностей до новіших версій
  3. Зміна 'цільової платформи' між x86, Itanium, x64 або anycpu
  4. Побудова / тестування на іншому встановленні фреймворку (тобто встановлення 3.5 у вікні .Net 2.0 дозволяє викликам API, для чого потрібен .Net 2.0 SP2)

Зміни завантаження / конфігурації:

  1. Додавання / видалення / зміна спеціальних параметрів конфігурації (наприклад, налаштування App.config)
  2. При великому використанні IoC / DI в сучасних додатках необхідно щось перенастроїти та / або змінити завантажувальний код на залежний від DI код.

Оновлення:

Вибачте, я не усвідомлював, що єдиною причиною цього було для мене те, що я використовував їх у обмеженнях шаблонів.


"Додавання нового члена (захищеного від події), який використовує тип з іншої збірки." - IIRC, клієнту потрібно лише посилатися на залежні збори, які містять базові типи зборок, на які він уже посилається; він не повинен посилатися на збірки, які просто використовуються (навіть якщо типи є в підписах методів); Я не впевнений у цьому на 100%. Чи є у вас посилання на точні правила для цього? Також переміщення типу може бути безперервним, якщо TypeForwardedToAttributeвикористовується.
Павло Мінаєв

Що "TypeForwardedTo" - це новина для мене, я перевірю це. Що стосується іншого, я також не на 100% на це ... дозвольте мені побачити, чи можу я спростувати, і я оновлю пост.
csharptest.net

Тож не -Werrorнамагайтесь утримувати у своїй будівельній системі, яку ви постачаєте, з випуском тарілок. Цей прапор є найбільш корисним для розробника коду і найчастіше корисний для споживача.
binki

@binki чудовий момент, трактуючи попередження, оскільки помилок повинно бути достатньо лише у складі DEBUG.
csharptest.net

3

Додавання методів перевантаження для зменшення використання параметрів за замовчуванням

Вид перерви: тиха семантика на рівні джерела

Оскільки компілятор перетворює виклики методу з відсутніми значеннями параметрів за замовчуванням у явний виклик зі значенням за замовчуванням на стороні виклику, надається сумісність для існуючого компільованого коду; метод з правильним підписом буде знайдений для всіх раніше складеного коду.

З іншого боку, виклики без використання необов'язкових параметрів тепер компілюються як виклик до нового методу, у якого відсутній необов'язковий параметр. Все це все ще працює нормально, але якщо зателефонований код знаходиться в іншій збірці, щойно складений код, який викликає його, тепер залежить від нової версії цієї збірки. Розгортання збірок, що викликають рефакторований код, не розгортаючи також збірку, в якій знаходиться рефакторований код, призводить до винятку "методу не знайдено".

API перед зміною

  public int MyMethod(int mandatoryParameter, int optionalParameter = 0)
  {
     return mandatoryParameter + optionalParameter;
  }    

API після зміни

  public int MyMethod(int mandatoryParameter, int optionalParameter)
  {
     return mandatoryParameter + optionalParameter;
  }

  public int MyMethod(int mandatoryParameter)
  {
     return MyMethod(mandatoryParameter, 0);
  }

Зразок коду, який все ще працюватиме

  public int CodeNotDependentToNewVersion()
  {
     return MyMethod(5, 6); 
  }

Зразок коду, який тепер залежить від нової версії при складанні

  public int CodeDependentToNewVersion()
  {
     return MyMethod(5); 
  }

1

Перейменування інтерфейсу

Вигляд перерви: джерело та двійкове

Мови, які стосуються: швидше за все, тестовані на C #.

API перед зміною:

public interface IFoo
{
    void Test();
}

public class Bar
{
    IFoo GetFoo() { return new Foo(); }
}

API після змін:

public interface IFooNew // Of the exact same definition as the (old) IFoo
{
    void Test();
}

public class Bar
{
    IFooNew GetFoo() { return new Foo(); }
}

Зразок коду клієнта, який працює, але згодом порушується:

new Bar().GetFoo().Test(); // Binary only break
IFoo foo = new Bar().GetFoo(); // Source and binary break

1

Спосіб перевантаження з параметром нульового типу

Вид: перерва на рівні джерела

Мови, на які впливає: C #, VB

API перед зміною:

public class Foo
{
    public void Bar(string param);
}

API після зміни:

public class Foo
{
    public void Bar(string param);
    public void Bar(int? param);
}

Зразок коду клієнта, який працює до зміни, і порушений після нього:

new Foo().Bar(null);

Виняток: виклик неоднозначний між наступними методами чи властивостями.


0

Перехід до методу розширення

Вид: перерва на рівні джерела

Мови, на які впливає: C # v6 та новіші (можливо, інші?)

API перед зміною:

public static class Foo
{
    public static void Bar(string x);
}

API після зміни:

public static class Foo
{
    public void Bar(this string x);
}

Зразок коду клієнта, який працює перед зміною і розбивається після нього:

using static Foo;

class Program
{
    static void Main() => Bar("hello");
}

Детальніше: https://github.com/dotnet/csharplang/isissue/665

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.