Я хотел бы собрать как можно больше информации об управлении версиями API в .NET/CLR, а также о том, как изменения API делают или не разрушают клиентские приложения. Сначала определим некоторые термины:
Изменение API - изменение общедоступного определения типа, включая любого из его публичных пользователей. Это включает в себя изменение типов и имен участников, изменение базового типа типа, добавление/удаление интерфейсов из списка реализованных интерфейсов типа, добавление/удаление элементов (включая перегрузки), изменение видимости элемента, метод переименования и параметры типа, добавление значений по умолчанию для параметров метода, добавления/удаления атрибутов для типов и элементов и добавления/удаления параметров типового типа для типов и элементов (я пропустил что-нибудь?). Это не включает никаких изменений в телах членов или каких-либо изменений в частных членах (т.е. Мы не учитываем Reflection).
Разрыв на двоичном уровне - изменение API, которое приводит к сбоям клиента, скомпилированным по сравнению со старой версией API, потенциально не загружаемой новой версией. Пример: изменение сигнатуры метода, даже если он позволяет вызываться так же, как и раньше (т.е.: Void для возврата значений значений по умолчанию для параметра/параметра по умолчанию).
Разрыв на уровне источника - изменение API, в результате чего существующий код написан для компиляции с более старой версией API, потенциально не скомпилированной с новой версией. Однако уже скомпилированные клиентские сборки работают по-прежнему. Пример: добавление новой перегрузки, которая может привести к двусмысленности в вызовах методов, которые были однозначными ранее.
Изменение тихой семантики на уровне исходного кода - изменение API, в результате чего существующий код, написанный для компиляции с более старой версией API, спокойно меняет свою семантику, например. путем вызова другого метода. Однако код должен продолжать компиляцию без предупреждений/ошибок, и ранее скомпилированные сборки должны работать по-прежнему. Пример: реализация нового интерфейса в существующем классе, в результате которого при перегрузке выбирается другая перегрузка.
Конечная цель состоит в том, чтобы каталогизировать как можно больше изменчивых и спокойных семантических API-интерфейсов, а также описывать точный эффект разлома, и какие языки не затрагиваются и не затрагиваются им. Чтобы расширить последнее: хотя некоторые изменения затрагивают все языки повсеместно (например, добавление нового элемента в интерфейс приведет к нарушению реализации этого интерфейса на любом языке), некоторые требуют очень специфической семантики языка для входа в игру, чтобы получить перерыв. Обычно это включает перегрузку методов и, в общем, что-то, что связано с неявными преобразованиями типов. Кажется, что нет никакого способа определить "наименьший общий знаменатель" здесь даже для CLS-совместимых языков (т.е. тех, которые соответствуют, по крайней мере, правилам "потребителя CLS", как определено в спецификации CLI), хотя я буду признателен, если кто-то исправляет меня как неправильный здесь - так что это должно будет идти языком по языку. Наиболее интересными являются, естественно, те, которые поставляются с .NET из коробки: С#, VB и F #; но другие, такие как IronPython, IronRuby, Delphi Prism и т.д. также актуальны. Чем больше углового случая, тем интереснее это будет - такие вещи, как удаление членов, довольно очевидны, но тонкие взаимодействия между, например, перегрузка метода, необязательные/параметры по умолчанию, вывод лямбда-типа и операторы преобразования могут быть очень неожиданными.
Несколько примеров, чтобы запустить это:
Добавление новых перегрузок методов
Вид: разрыв исходного уровня
Языки затронуты: С#, 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]);
Добавление новых неявных преобразований оператора
Вид: разрыв исходного уровня.
Языки затронуты: С#, 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
.
Добавление новых методов экземпляра
Вид: тихая семантика на уровне исходного кода.
Языки затронуты: С#, 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 вызывались как статические методы.