Поделиться через


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

На протяжении всей истории своего развития в .NET по возможности поддерживался высокий уровень совместимости между версиями и вариантами этой платформы. Хотя .NET 5 (и .NET Core) и более поздних версий можно считать новой технологией в сравнении с платформой .NET Framework, возможность независимого от .NET Framework развития данной реализации .NET ограничивается следующими двумя факторами.

  • Большое количество разработчиков разрабатывали ранее и продолжают разрабатывать приложения для .NET Framework. Все они рассчитывают на согласованное поведение разных реализаций .NET.

  • Проекты библиотек .NET Standard позволяют разработчикам создавать библиотеки для распространенных API, которые совместно используются в .NET Framework и .NET 5 (и .NET Core) и более поздних версий. Разработчики ожидают, что библиотеки из приложения .NET 5 будут вести себя точно так же, как аналогичные библиотеки в приложении .NET Framework.

Кроме совместимости разных реализаций .NET, разработчики также ожидают высокий уровень совместимости между версиями данной реализации .NET. В частности, код, написанный для более ранней версии .NET Core, должен без проблем работать в .NET 5 и любой последующей версии. Более того, многие разработчики ожидают, что новые API в свежих версиях .NET будут также совместимы с предварительными версиями, в которых впервые появились эти API.

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

В следующих разделах описываются категории изменений, внесенных в API .NET, и их влияние на совместимость приложений. Изменения разрешены (), запрещены (✔️❌) или требуют решения и оценки того, как прогнозируемое, очевидное и согласованное предыдущее поведение (❓).

Примечание.

  • Эти критерии не только разъясняют оценку изменений в библиотеках .NET, но и могут помочь разработчикам оценивать изменения в собственных библиотеках, предназначенных для различных реализаций и версий .NET.
  • Сведения о категориях совместимости, например переадресации и обратной совместимости, см. в статье о том, как изменения кода могут повлиять на совместимость.

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

Изменения в этой категории изменяют общую контактную зону для типа. Большинство изменений в этой категории запрещены, так как они нарушают обратную совместимость (возможность выполнять приложения, разработанные для предыдущих версий API, без повторной компиляции для более поздней версии).

Типы

  • ✔️ РАЗРЕШЕНО: удаление реализации интерфейса из типа, когда интерфейс уже реализован базовым типом

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Добавление новой реализации интерфейса в тип

    Это допустимое изменение, так как оно не сказывается отрицательно на существующих клиентах. Чтобы новая реализация оставалась допустимой, любые изменения типа должны выполняться в пределах допустимых изменений, которые определены здесь. Следует соблюдать предельную осторожность при добавлении интерфейсов, которые напрямую влияют на способность конструктора или сериализатора создавать код или данные, которые нельзя использовать на нижнем уровне. Пример — интерфейс ISerializable.

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Знакомство с новым базовым классом

    Тип можно включать в иерархию между двумя существующими типами, если он не включает новые абстрактные элементы и не изменяет семантику или поведение существующих типов. Например, в .NET Framework 2.0 класс DbConnection стал новым базовым классом для SqlConnection с наследованием напрямую от Component.

  • ✔️ РАЗРЕШЕНО. Перемещение типа из одной сборки в другую

    Старой сборке следует присвоить метку TypeForwardedToAttribute, указывающую на новую сборку.

  • ✔️ РАЗРЕШЕНО: изменение типа структуры на readonly struct тип

    Изменение типа readonly struct на тип struct запрещено.

  • ✔️ РАЗРЕШЕНО. Добавление запечатаемого или абстрактного ключевого слова в тип, если нет доступных (общедоступных или защищенных) конструкторов

  • ✔️ РАЗРЕШЕНО: расширение видимости типа

  • DISALLOWED: изменение пространства имен или имени типа

  • ЗАПРЕЩЕНО: переименование или удаление общедоступного типа

    Это изменение нарушает весь код, в котором использовался переименованный или удаленный тип.

    Примечание.

    В редких случаях .NET может удалить общедоступный API. Дополнительные сведения см. в статье об удалении API в .NET. Сведения о . Политика поддержки NET см . в статье о политике поддержки .NET.

  • DISALLOWED: изменение базового типа перечисления

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

  • DISALLOWED: Печать типа, который ранее не был незапечатан

  • DISALLOWED: добавление интерфейса в набор базовых типов интерфейса

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

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Удаление класса из набора базовых классов или интерфейса из набора реализованных интерфейсов

    Есть одно исключение из правила удаления интерфейса: вы можете добавить реализацию интерфейса, наследуемую от удаленного интерфейса. Например, можно удалить IDisposable, если тип или интерфейс теперь реализуют IComponent с реализацией IDisposable.

  • DISALLOWED: изменение readonly struct типа на тип структуры

    Обратите внимание, что изменение типа struct на тип readonly struct разрешено.

  • DISALLOWED: изменение типа структуры на ref struct тип, и наоборот

  • ЗАПРЕЩЕНО: уменьшение видимости типа

    При этом увеличение видимости типа разрешено.

Участники

  • ✔️ РАЗРЕШЕНО. Расширение видимости элемента, который не является виртуальным

  • ✔️ РАЗРЕШЕНО: добавление абстрактного элемента в открытый тип, не имеющий доступных (общедоступных или защищенных) конструкторов, или тип запечатан.

    При этом добавление абстрактного элемента в тип с доступными конструкторами (открытыми или защищенными) и в незапечатанный тип (sealed) разрешено.

  • ✔️ РАЗРЕШЕНО. Ограничение видимости защищенного элемента, если тип не имеет доступных (общедоступных или защищенных ) конструкторов или тип запечатан.

  • ✔️ РАЗРЕШЕНО. Перемещение члена в класс выше в иерархии, чем тип, из которого он был удален

  • ✔️ РАЗРЕШЕНО. Добавление или удаление переопределения

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

  • ✔️ РАЗРЕШЕНО. Добавление конструктора в класс вместе с конструктором без параметров, если у класса ранее не было конструкторов

    При этом добавление конструктора в класс, ранее не имевший конструкторов, без добавления конструктора без параметров, не разрешено.

  • ✔️ РАЗРЕШЕНО: изменение элемента с абстрактного на виртуальный

  • ✔️ РАЗРЕШЕНО: переход с ref readonly возвращаемого ref значения (за исключением виртуальных методов или интерфейсов)

  • ✔️ РАЗРЕШЕНО: удаление считывания из поля, если статический тип поля не является изменяемым типом значения

  • ✔️ РАЗРЕШЕНО: вызов нового события, которое ранее не было определено

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Добавление нового поля экземпляра в тип

    Это изменение влияет на сериализацию.

  • DISALLOWED: переименование или удаление общедоступного члена или параметра

    Это изменение нарушает весь код, в котором использовался переименованный или удаленный элемент либо параметр.

    Сюда относятся удаление и переименование методов получения и определения свойств, а также элементов перечисления.

  • DISALLOWED: добавление члена в интерфейс

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

  • DISALLOWED: изменение значения общедоступной константы или элемента перечисления

  • DISALLOWED: изменение типа свойства, поля, параметра или возвращаемого значения

  • ЗАПРЕЩЕНО: добавление, удаление или изменение порядка параметров

  • DISALLOWED: добавление или удаление ключевого слова in, out или ref из параметра

  • DISALLOWED: переименование параметра (включая изменение его регистра)

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

  • ЗАПРЕЩЕНО: изменение возвращаемого ref значения на возвращаемое ref readonly значение

  • ❌️ ЗАПРЕЩЕНО: переход от ref readonly возвращаемого ref значения к виртуальному методу или интерфейсу

  • DISALLOWED: добавление или удаление абстрактного элемента

  • DISALLOWED: удаление виртуального ключевого слова из члена

  • DISALLOWED: добавление виртуального ключевого слова в элемент

    Такое изменение часто не является критическим, так как компилятор C# обычно выдает инструкции callvirt на промежуточном языке (IL) для вызова невиртуальных методов (callvirt, в отличие об обычного кода, выполняет проверку значений null). При этом такое поведение не может считаться стабильным по следующим причинам:

    • .NET используется не только с C#, но и с другими языками.

    • Компилятор C# продолжает попытки оптимизировать callvirt в обычный вызов, если целевой метод не является виртуальным и с высокой вероятностью не имеет значения null (например, метод с доступом с использованием оператора распространения значений null ?.).

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

  • ЗАПРЕЩЕНО: создание абстрактного виртуального члена

    Виртуальный элемент предоставляет реализацию метода, которую можно переопределить производным классом. Абстрактный элемент не предоставляет реализацию и должен быть переопределен.

  • DISALLOWED: добавление запечатаемого ключевого слова в член интерфейса

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

  • DISALLOWED: добавление абстрактного элемента в открытый тип, имеющий доступные (общедоступные или защищенные) конструкторы и не запечатанный

  • DISALLOWED: добавление или удаление статического ключевого слова из элемента

  • DISALLOWED: добавление перегрузки, которая исключает существующую перегрузку и определяет другое поведение.

    Такое изменение нарушает логику существующих клиентов, которые зависели от предыдущей перегрузки. Например, если у класса есть одна версия метода, которая принимает UInt32, существующий получатель будет успешно привязан к этой перегрузке при отправке значения Int32. Но если вы добавите перегрузку, которая принимает Int32, при повторной компиляции или при использовании позднего связывания компилятор будет выполнять привязку к новой перегрузке. Любые изменения, приводящие к разным реакциям на события, считаются критическими.

  • DISALLOWED: добавление конструктора в класс, который ранее не имел конструктора без добавления конструктора без добавления конструктора без параметров

  • ❌️ ЗАПРЕЩЕНО: добавление чтения в поле

  • ЗАПРЕЩЕНО: уменьшение видимости элемента

    Сюда входит ограничение видимости защищенного элемента, если имеются доступные (public или protected) конструкторы, но тип не является запечатанным. Если это не так, снижение видимости защищенного элемента разрешено.

    Увеличение видимости типа разрешено.

  • ЗАПРЕЩЕНО: изменение типа элемента

    Возвращаемое значение метода или типа свойства или поля изменить нельзя. Например, сигнатуру метода, который возвращает Object, нельзя изменить так, чтобы он возвращал String, или наоборот.

  • DISALLOWED: добавление поля экземпляра в структуру, которая не имеет неопубликованных полей

    Если у структуры есть только открытые поля или нет полей вообще, вызывающие могут объявлять локальные локальные типы структуры без вызова конструктора структуры или первого инициализации локального default(T), если все общедоступные поля задаются в структуре перед первым использованием. Добавление новых полей — общедоступных или неопубликованных — в такую структуру — это критическое изменение источника для этих вызывающих пользователей, так как компилятору теперь потребуется инициализировать дополнительные поля.

    Кроме того, добавление новых полей — общедоступных или неопубликованных — в структуру без полей или только общедоступных полей — это двоичное критическое изменение вызывающих элементов, примененных [SkipLocalsInit] к коду. Так как компилятор не знал об этих полях во время компиляции, он может выдавать IL, который не полностью инициализирует структуру, что приводит к созданию структуры из неинициализированных данных стека.

    Если у структуры есть какие-либо неопубликованные поля, компилятор уже применяет инициализацию с помощью конструктора или default(T), а добавление новых полей экземпляра не является критическим изменением.

  • ЗАПРЕЩЕНО: запуск существующего события, когда он никогда не был запущен раньше

Изменение поведения

Сборки

  • ✔️ РАЗРЕШЕНО. Перенос сборки, если те же платформы по-прежнему поддерживаются

  • ЗАПРЕЩЕНО. Изменение имени сборки

  • ЗАПРЕЩЕНО: изменение открытого ключа сборки

Свойства, поля, параметры и возвращаемые значения

  • ✔️ РАЗРЕШЕНО: изменение значения свойства, поля, возвращаемого значения или параметра out на более производный тип

    Например, метод, возвращающий тип Object, может возвращать экземпляр String. (Но при этом сигнатура метода не должна изменяться.)

  • ✔️ РАЗРЕШЕНО: увеличение диапазона принятых значений для свойства или параметра, если член не является виртуальным.

    Можно расширять диапазон значений, которые передаются методу или возвращаются из него, но нельзя расширять тип параметра или элемента. Например, диапазон передаваемых методу значений можно расширить с 0–124 до 0–255, но нельзя изменить тип параметра с Byte на Int32.

  • DISALLOWED: увеличение диапазона принятых значений для свойства или параметра, если член является виртуальным

    Такое изменение нарушает существующие переопределенные элементы, которые не будут правильно работать с расширенным диапазоном значений.

  • DISALLOWED: уменьшение диапазона принятых значений для свойства или параметра

  • DISALLOWED: увеличение диапазона возвращаемых значений для свойства, поля, возвращаемого значения или параметра out

  • DISALLOWED: изменение возвращаемых значений для свойства, поля, возвращаемого метода или параметра out

  • DISALLOWED: изменение значения по умолчанию свойства, поля или параметра

    Изменение или удаление значения параметра по умолчанию не является двоичным разрывом. Удаление значения по умолчанию параметра — это разрыв источника, и изменение значения по умолчанию параметра может привести к разрыву поведения после перекомпиляции.

    По этой причине удаление значений по умолчанию параметров допустимо в конкретном случае перемещения этих значений по умолчанию в новую перегрузку метода, чтобы устранить неоднозначность. Например, рассмотрим существующий метод MyMethod(int a = 1). Если вы вводите перегрузку MyMethod с двумя необязательными параметрами a и bможете сохранить совместимость, переместив значение a по умолчанию в новую перегрузку. Теперь две перегрузки и MyMethod(int a) MyMethod(int a = 1, int b = 2). Этот шаблон позволяет MyMethod() компилировать.

  • DISALLOWED: изменение точности числового возвращаемого значения

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Изменение синтаксического анализа входных данных и создание новых исключений (даже если поведение синтаксического анализа не указано в документации

Исключения

  • ✔️ РАЗРЕШЕНО: создание более производного исключения, чем существующее исключение

    Так как новое исключение является подклассом существующего исключения, существующий код обработки исключений будет обрабатывать это исключение. Например, в .NET Framework 4 методы создания и получения языка и региональных параметров теперь вызывают исключение CultureNotFoundException вместо ArgumentException, если не могут найти язык и региональные параметры. Так как CultureNotFoundException является производным от ArgumentException, это изменение считается допустимым.

  • ✔️ РАЗРЕШЕНО: создание более конкретного исключения, чем NotSupportedException, NotImplementedExceptionNullReferenceException

  • ✔️ РАЗРЕШЕНО: создание исключения, которое считается невосстановимым

    Неустранимые исключения не нужно перехватывать, они попадают в обработчик catch-all на верхнем уровне. Это означает, что у пользователей нет кода, который перехватывает эти явные исключения. Неустранимыми считаются следующие исключения:

  • ✔️ РАЗРЕШЕНО: создание нового исключения в новом пути кода

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

  • ✔️ РАЗРЕШЕНО: удаление исключения для обеспечения более надежного поведения или новых сценариев

    Например, метод Divide, который ранее обрабатывал только положительные значения и вызвал исключение ArgumentOutOfRangeException, можно изменить так, чтобы он поддерживал положительные и отрицательные значения без вызова исключения.

  • ✔️ РАЗРЕШЕНО: изменение текста сообщения об ошибке

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

  • ЗАПРЕЩЕНО: создание исключения в любом другом случае, который не указан выше

  • ЗАПРЕЩЕНО: удаление исключения в любом другом случае, который не указан выше

Атрибуты

  • ✔️ РАЗРЕШЕНО. Изменение значения атрибута, который не наблюдается

  • DISALLOWED: изменение значения атрибута, наблюдаемого

  • ТРЕБУЕТСЯ РЕШЕНИЕ: удаление атрибута

    В большинстве случаев удаление атрибута (например, NonSerializedAttribute) является критическим изменением.

Поддержка платформы

  • ✔️ РАЗРЕШЕНО: поддержка операции на платформе, которая ранее не поддерживается

  • ЗАПРЕЩЕНО. Не поддерживается или теперь требуется определенный пакет обновления для операции, которая ранее поддерживалась на платформе.

Изменения внутренней реализации

  • ТРЕБУЕТСЯ РЕШЕНИЕ: изменение области поверхности внутреннего типа

    Такие изменения обычно разрешены, хотя они изменяют закрытое отражение. Эти изменения могут быть не разрешены в некоторых случаях, если популярные сторонние библиотеки или большое количество разработчиков полагаются на внутренние API.

  • ТРЕБУЕТСЯ РЕШЕНИЕ. Изменение внутренней реализации члена

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

  • ✔️ РАЗРЕШЕНО: повышение производительности операции

    Возможность изменять производительность операции очень важна, но такие изменения могут нарушить код, который зависит от текущей скорости операции. Это особенно важно для кода, который зависит от скорости выполнения асинхронных операций. Изменение производительности не должно влиять на другие аспекты поведения API, в противном случае изменение будет считаться критическим.

  • ✔️ РАЗРЕШЕНО: косвенно (и часто отрицательно) изменение производительности операции

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

  • DISALLOWED: изменение синхронного API на асинхронный (и наоборот)

Изменения в коде

  • ✔️ РАЗРЕШЕНО: добавление парамов в параметр

  • DISALLOWED: изменение структуры на класс и наоборот

  • DISALLOWED: добавление проверенного ключевого слова в блок кода

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

  • DISALLOWED: удаление парам из параметра

  • DISALLOWED: изменение порядка, в котором выполняются события

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

  • ЗАПРЕЩЕНО: удаление вызова события для заданного действия

  • DISALLOWED: изменение количества заданных событий вызывается

  • DISALLOWED: Добавление FlagsAttribute в тип перечисления

См. также