Полное руководство к повреждающим API изменениям в.NET

Мне пришлось ждать 4-5 месяцев. так что это выглядит довольно стандартно для такого длительного периода ожидания.

219
задан 4 revs, 4 users 66% 4 March 2015 в 01:32
поделиться

7 ответов

Изменение сигнатуры метода

Тип: Разрыв на двоичном уровне

Затронутые языки: 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);
40
ответ дан 23 November 2019 в 04:10
поделиться

Метод перегрузки с параметром обнуляемого типа

Вид: Разрыв уровня источника

Затрагиваемые языки: 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);

Исключение: вызов неоднозначен между следующими методами или свойствами.

1
ответ дан 23 November 2019 в 04:10
поделиться

Когда я обнаружил, этот был очень неочевидным, особенно в свете разницы в той же ситуации для интерфейсов. Это совсем не перерыв, но достаточно удивительно, что я решил включить его:

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

Тип: не перерыв!

Затронутые языки: нет (т.е. ни один не сломан)

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, сгенерированный для явного переопределения, такой же, как и для явной реализации). К моему удивлению, это не так - хотя сгенерированный IL по-прежнему указывает, что BarOverride переопределяет Foo :: Bar , а не FooBase :: Bar , загрузчик сборки достаточно умен, чтобы правильно заменять один на другой без каких-либо претензий - очевидно, тот факт, что Foo является классом, вот в чем разница. Иди разбери ...

загрузчик сборок достаточно умен, чтобы правильно заменять один на другой без каких-либо претензий - очевидно, тот факт, что Foo является классом, вот что имеет значение. Иди разбери ...

загрузчик сборок достаточно умен, чтобы правильно заменять один на другой без каких-либо нареканий - очевидно, тот факт, что Foo является классом, вот что отличает. Поймите ...

26
ответ дан 23 November 2019 в 04:10
поделиться

Это, возможно, не столь очевидный частный случай «добавления / удаления элементов интерфейса», и я решил, что он заслуживает собственная запись в свете другого случая, о котором я собираюсь опубликовать позже. Итак:

Рефакторинг элементов интерфейса в базовый интерфейс

Тип: разрывы как на уровне исходного, так и на двоичном уровнях

Затронутые языки: 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 требуют точного имени интерфейса в объявлении реализации члена интерфейса; таким образом, если член перемещается в базовый интерфейс, код больше не будет компилироваться.

Двоичный разрыв происходит из-за того, что методы интерфейса полностью определены в сгенерированном IL для явных реализаций, и имя интерфейса также должно быть точным.

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

и имя интерфейса также должно быть точным.

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

и имя интерфейса также должно быть точным.

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

19
ответ дан 23 November 2019 в 04:10
поделиться

Изменение API:

  1. Добавление атрибута [Obsolete] (вы как бы покрыли это упоминанием атрибутов; однако это может быть критическим изменением при использовании предупреждения как ошибки.)

Разрыв на двоичном уровне:

  1. Перемещение типа из одной сборки в другую
  2. Изменение пространства имен типа
  3. Добавление типа базового класса из другой сборки.
  4. Добавление нового члена (защищенного от события), который использует тип из другой сборки (Class2) в качестве ограничения аргумента шаблона.

     protected void Something  (), где T: Class2 {}
    
  5. Изменение дочернего класса (Class3) на производный от типа в другой сборке, когда класс используется в качестве аргумента шаблона для этого класса.

     защищенный класс Class3: Class2 {}
    protected void Something  () где T: Class3 {}
    

Изменение тихой семантики на уровне источника:

  1. Добавление / удаление / изменение переопределений Equals (), GetHashCode () или ToString ()

(не уверен, где они подходят)

Изменения развертывания:

  1. Добавление / удаление зависимостей / ссылок
  2. Обновление зависимостей до более новых версий
  3. Изменение «целевой платформы» между x86, Itanium, x64 или anycpu
  4. Сборка / тестирование на другой установке фреймворка (т.е. установка 3.5 на Блок .Net 2.0 разрешает вызовы API, которые затем требуют .Net 2.0 SP2)

Bootstrap / Изменения конфигурации:

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

Обновление:

Извините, я не "

4
ответ дан 23 November 2019 в 04:10
поделиться

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

Вид нарушения: исходный код

Затронутые языки: все

Реорганизация явной реализации интерфейса в неявную осуществляется более тонко в том, как это может сломать 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"
7
ответ дан 23 November 2019 в 04:10
поделиться

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

Вид нарушения: исходный и двоичный

Затронутые языки: все

На самом деле это просто вариант изменения метода доступность - это немного тоньше, поскольку легко упустить из виду тот факт, что не весь доступ к методам интерфейса обязательно осуществляется через ссылку на тип интерфейса.

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
9
ответ дан 23 November 2019 в 04:10
поделиться
Другие вопросы по тегам:

Похожие вопросы: