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