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