Управление версиями в C#

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

Разработка библиотек

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

Семантическое управление версиями

Семантическое версионирование (SemVer) — это соглашение об именовании, применяемое к версиям библиотеки для обозначения ключевых вех. В идеале сведения о версии, включаемые в библиотеку, должны помочь разработчикам определить совместимость с проектами, в которых используются предыдущие версии этой библиотеки.

Базовым вариантом SemVer является трехкомпонентный формат MAJOR.MINOR.PATCH, где:

  • MAJOR увеличивается при внесении изменений, приводящих к несовместимости API;
  • MINOR увеличивается при добавлении функциональных возможностей с обеспечением обратной совместимости;
  • PATCH увеличивается при исправлении ошибок с обеспечением обратной совместимости.

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

Обратная совместимость

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

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

  • Виртуальные методы: когда вы делаете виртуальный метод невиртуальным в новой версии, это означает, что проекты, в которых этот метод переопределяется, необходимо будет обновить. Это очень существенное изменение, которое настоятельно не рекомендуется производить.
  • Сигнатуры методов. При обновлении поведения метода необходимо также изменить ее сигнатуру, вместо этого следует создать перегрузку, чтобы вызов кода в этот метод по-прежнему работал. Прежнюю сигнатуру метода всегда можно настроить так, чтобы она вызывала новую сигнатуру, обеспечив тем самым согласованность реализаций.
  • Атрибут obsolete: с помощью этого атрибута можно указывать в коде классы или члены классов, которые являются нерекомендуемыми и, вероятно, будут удалены в будущих версиях. Благодаря этому разработчики, использующие вашу библиотеку, будут лучше подготовлены к существенным изменениям.
  • Необязательные аргументы метода: если вы делаете необязательные аргументы метода обязательными или меняете их значения по умолчанию, то код, который не задает эти аргументы, потребуется обновить.

Примечание.

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

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

Файл конфигурации приложения

Как разработчик .NET вы, весьма вероятно, встречали файл app.config в большинстве типов проектов. Этот простой файл конфигурации может очень упростить развертывание обновлений. В основном библиотеки следует проектировать так, чтобы сведения, которые, скорее всего, будут меняться регулярно, хранились в файле app.config. Благодаря этому при изменении таких сведений достаточно будет заменить файл конфигурации предыдущей версии на новый, не перекомпилируя библиотеку.

Использование библиотек

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

К счастью, экосистема C# и .NET предоставляет возможности и методы, которые позволяют обновлять приложения для работы с новыми версиями библиотек, в которые были внесены существенные изменения.

Перенаправление привязки сборок

С помощью файла app.config можно изменить версию библиотеки, используемую приложением. Добавив так называемое перенаправление привязки, вы можете использовать новую версию библиотеки, не перекомпилируя приложение. В приведенном ниже примере показано, как можно изменить файл приложения app.config так, чтобы оно использовало версию исправления 1.0.1 библиотеки ReferencedLibrary вместо версии 1.0.0, с которой оно изначально компилировалось.

<dependentAssembly>
    <assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
    <bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>

Примечание.

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

Создать...

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

Возьмем следующий пример:

public class BaseClass
{
    public void MyMethod()
    {
        Console.WriteLine("A base method");
    }
}

public class DerivedClass : BaseClass
{
    public new void MyMethod()
    {
        Console.WriteLine("A derived method");
    }
}

public static void Main()
{
    BaseClass b = new BaseClass();
    DerivedClass d = new DerivedClass();

    b.MyMethod();
    d.MyMethod();
}

Выходные данные

A base method
A derived method

В приведенном выше примере можно видеть, как в классе DerivedClass скрывается метод MyMethod, имеющийся в классе BaseClass. Таким образом, если в базовом классе в новой версии библиотеки появляется член, который уже имеется в вашем производном классе, можно просто использовать модификатор new для члена производного класса, чтобы скрыть соответствующий член базового класса.

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

override

Модификатор override означает, что производная реализация расширяет реализацию члена базового класса, а не скрывает его. К члену базового класса должен быть применен модификатор virtual.

public class MyBaseClass
{
    public virtual string MethodOne()
    {
        return "Method One";
    }
}

public class MyDerivedClass : MyBaseClass
{
    public override string MethodOne()
    {
        return "Derived Method One";
    }
}

public static void Main()
{
    MyBaseClass b = new MyBaseClass();
    MyDerivedClass d = new MyDerivedClass();

    Console.WriteLine("Base Method One: {0}", b.MethodOne());
    Console.WriteLine("Derived Method One: {0}", d.MethodOne());
}

Выходные данные

Base Method One: Method One
Derived Method One: Derived Method One

Модификатор override оценивается во время компиляции, и компилятор выдаст ошибку, если ему не удастся найти виртуальный член для переопределения.

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