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

227

Я хотел бы собрать как можно больше информации о версиях API в .NET / CLR и, в частности, о том, как изменения API нарушают или не нарушают клиентские приложения. Сначала давайте определим некоторые термины:

Изменение API - изменение в общедоступном определении типа, включая любого из его открытых членов. Это включает в себя изменение типа и имен элементов, изменение базового типа типа, добавление / удаление интерфейсов из списка реализованных интерфейсов типа, добавление / удаление элементов (включая перегрузки), изменение видимости элемента, переименование метода и параметров типа, добавление значений по умолчанию для параметров метода, добавление / удаление атрибутов для типов и членов, а также добавление / удаление параметров общих типов для типов и членов (я что-то пропустил?). Это не включает какие-либо изменения в членских органах или какие-либо изменения в частных членах (т.е. мы не принимаем во внимание Рефлексию).

Разрыв двоичного уровня - изменение API, в результате которого клиентские сборки, скомпилированные для более старой версии API, потенциально не загружаются с новой версией. Пример: изменение сигнатуры метода, даже если он позволяет вызываться так же, как и раньше (то есть: void для возврата значений по умолчанию для типов / параметров).

Разрыв на уровне исходного кода - изменение 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
@ Роберт: ваша ссылка о чем-то совсем другом - она ​​описывает конкретные критические изменения в самой .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, которые ссылались на эти утилиты, были перекомпилированы и выпущены, так что это вызывает исключение метода notfound.
Джастин Друри,
1
Это восходит к тому факту, что возвращаемые типы не учитываются для сигнатуры метода. Вы также не можете перегрузить две функции исключительно на основе типа возвращаемого значения. Та же проблема.
Джейсон Шорт
1
ответ на этот ответ: кто-нибудь знает значение добавления dotnet4 defaultvalue 'public static void bar (int i = 0);' или изменить это значение по умолчанию с одного значения на другое?
k3b
1
Для тех, кто собирается попасть на эту страницу, я думаю для C # (и «я думаю» большинства других языков ООП), Return Types не способствует подписи метода. Да, ответ правильный, что изменения подписи способствуют изменению двоичного уровня. НО пример не кажется правильным ИМХО правильный пример, который я могу представить, ДО ДО публичной десятичной суммы Sum (int a, int b) После публичной десятичной суммы Sum (десятичная a, десятичная b) Пожалуйста, обратитесь к этой ссылке MSDN 3.6 Подписи и перегрузка
Бхану Чхабра
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 #).

Eldritch Conundrum
источник
2
это серьезное изменение даже для уровня исходного кода: Func<int> f = Foo;// это не удастся с измененной подписью
Vagaus
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, сгенерированный для явного переопределения, такой же, как и для явной реализации). К моему удивлению, это не тот случай - хотя сгенерированный IL по-прежнему указывает, что BarOverrideпереопределения, Foo::Barа не FooBase::Barзагрузчик сборок, достаточно умны, чтобы корректно заменять одно на другое без каких-либо претензий - очевидно, тот факт, что Fooэто класс, является тем, что имеет значение. Пойди разберись ...

Павел Минаев
источник
3
Пока базовый класс находится в одной сборке. В противном случае это бинарное изменение.
Джереми
@ Джереми, какой код ломается в этом случае? Будет ли использование Baz () внешним вызывающим пользователем прервано или это проблема только тех людей, которые пытаются расширить Foo и переопределить Baz ()?
ChaseMedallion
@ChaseMedallion ломается, если вы являетесь пользователем из вторых рук. Например, скомпилированная DLL ссылается на старую версию Foo, и вы ссылаетесь на эту скомпилированную DLL, но также используете более новую версию Foo DLL. Он ломается со странной ошибкой, или, по крайней мере, он сделал для меня в библиотеках, которые я разработал ранее.
Джереми
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 требуют точного имени интерфейса в объявлении реализации члена интерфейса; таким образом, если член перемещается в базовый интерфейс, код больше не будет компилироваться.

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

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

Павел Минаев
источник
Это не верно для всех языков. Для VB это не смена исходного кода. Для C # это так.
Джереми
Так Implements IFoo.Barбудет прозрачно ссылаться IFooBase.Bar?
Павел Минаев
Да, это действительно так, вы можете ссылаться на член прямо или косвенно через интерфейс наследования при его реализации. Тем не менее, это всегда бинарные изменения.
Джереми
15

Изменение порядка перечисляемых значений

Вид разрыва: Изменение семантики тихого семантики на уровне источника / двоичного уровня

Затрагиваемые языки: все

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

Еще хуже то, что тихие разрывы двоичного уровня могут быть введены, если клиентский код не перекомпилируется с новой версией API. Enum-значения являются константами времени компиляции, и любое их использование запекается в IL клиентской сборки. Этот случай может быть особенно трудно заметить время от времени.

API до изменения

public enum Foo
{
   Bar,
   Baz
}

API после изменения

public enum Foo
{
   Baz,
   Bar
}

Пример клиентского кода, который работает, но впоследствии не работает:

Foo.Bar < Foo.Baz
glopes
источник
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. С учетом того, что компилятор управляет правилами разрешения, он переключается с версии в производном классе на версию в базовом классе.
Л.Бушкин
Вы забыли yield return "Bar":), но да, я вижу, куда это идет сейчас - foreachвсегда вызывает открытый метод named GetEnumerator, даже если это не настоящая реализация для IEnumerable.GetEnumerator. Кажется, у этого есть еще один угол: даже если у вас есть только один класс, и он реализуется IEnumerableявно, это означает, что это изменение источника, добавляющее к нему открытый метод GetEnumerator, потому что теперь он foreachбудет использовать этот метод поверх реализации интерфейса. Также эта же проблема применима и к IEnumeratorреализации ...
Павел Минаев
6

Изменение поля на свойство

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

Затрагиваемые языки: Visual Basic и C # *

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

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"
оборота Hagelt18
источник
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

Параметры Ref

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

Добавление переопределения метода с той же сигнатурой, за исключением того, что один параметр передается по ссылке, а не по значению, приведет к тому, что vb source, который ссылается на 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);
jswolf19
источник
4

Изменение API:

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

Разрыв двоичного уровня:

  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. Добавление / удаление / изменение переопределений Equals (), 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.

Обновить:

Извините, я не осознавал, что единственная причина, по которой это сломалось, заключалась в том, что я использовал их в шаблонных ограничениях.

csharptest.net
источник
«Добавление нового члена (защищенного от событий), который использует тип из другой сборки». - IIRC, клиент должен ссылаться только на зависимые сборки, которые содержат базовые типы сборок, на которые он уже ссылается; он не должен ссылаться на сборки, которые просто используются (даже если типы находятся в сигнатурах методов); Я не уверен на 100% в этом. У вас есть ссылка для точных правил для этого? Кроме того, перемещение типа может быть неразрывным, если TypeForwardedToAttributeиспользуется.
Павел Минаев
Это "TypeForwardedTo" для меня новость, я проверю это. Что касается другого, я также не на 100% на этом ... позвольте мне видеть, может ли repro, и я обновлю почту.
csharptest.net
Итак, не навязывайте -Werrorсвою систему сборки, которую вы поставляете с релизными тарболами. Этот флаг наиболее полезен для разработчика кода и чаще всего бесполезен для потребителя.
Бинки
@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
Aidiakapi
источник
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/issues/665

rory.ap
источник