Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом учебнике вы получите представление об управлении версиями в .NET. Кроме того, вы узнаете, какие факторы следует учитывать при управлении версиями библиотеки и обновлении до ее новой версии.
Версия языка
Компилятор C# является частью пакета SDK для .NET. По умолчанию компилятор выбирает версию языка C#, которая соответствует выбранному TFM для проекта. Если версия пакета SDK больше выбранной платформы, компилятор может использовать более высокую языковую версию. Вы можете изменить значение по умолчанию, задав LangVersion элемент в проекте. Вы можете узнать, как описано в нашей статье о параметрах компилятора.
Предупреждение
LangVersion Настройка элемента latest не рекомендуется. Параметр означает, latest что установленный компилятор использует последнюю версию. Это может измениться с компьютера на компьютер, что делает сборки ненадежными. Кроме того, он включает функции языка, для которых могут потребоваться функции среды выполнения или библиотеки, не включенные в текущий пакет SDK.
Разработка библиотек
Если вы занимались разработкой библиотек .NET для открытого использования, то, скорее всего, вам приходилось развертывать обновления. Способ выполнения этой процедуры важен, так как необходимо обеспечить плавный перевод существующего кода на новую версию библиотеки. Ниже описывается ряд моментов, которые следует учитывать при создании выпуска.
Семантическое версионирование
Семантическое версионирование (SemVer) — это соглашение об именовании, применяемое к версиям библиотеки для обозначения ключевых вех. В идеале сведения о версии, включаемые в библиотеку, должны помочь разработчикам определить совместимость с проектами, в которых используются предыдущие версии этой библиотеки.
Базовым вариантом SemVer является трехкомпонентный формат MAJOR.MINOR.PATCH, где:
-
MAJORувеличивается при внесении изменений, приводящих к несовместимости API; -
MINORувеличивается при добавлении функциональных возможностей с обеспечением обратной совместимости; -
PATCHувеличивается при исправлении ошибок с обеспечением обратной совместимости.
Общие сведения о добавочных версиях с примерами
Чтобы уточнить, когда следует увеличить каждый номер версии, ниже приведены конкретные примеры:
Увеличение ОСНОВНЫХ версий (несовместимые изменения API)
Эти изменения требуют от пользователей изменения кода для работы с новой версией:
Удаление общедоступного метода или свойства:
// Version 1.0.0 public class Calculator { public int Add(int a, int b) => a + b; public int Subtract(int a, int b) => a - b; // This method exists } // Version 2.0.0 - MAJOR increment required public class Calculator { public int Add(int a, int b) => a + b; // Subtract method removed - breaking change! }Изменение сигнатур методов:
// Version 1.0.0 public void SaveFile(string filename) { } // Version 2.0.0 - MAJOR increment required public void SaveFile(string filename, bool overwrite) { } // Added required parameterИзменение поведения существующих методов таким образом, чтобы сломать ожидания:
// Version 1.0.0 - returns null when file not found public string ReadFile(string path) => File.Exists(path) ? File.ReadAllText(path) : null; // Version 2.0.0 - MAJOR increment required public string ReadFile(string path) => File.ReadAllText(path); // Now throws exception when file not found
Увеличение минорных версий (обратная совместимость функциональности)
Эти изменения добавляют новые функции без нарушения существующего кода:
Добавление новых общедоступных методов или свойств:
// Version 1.0.0 public class Calculator { public int Add(int a, int b) => a + b; } // Version 1.1.0 - MINOR increment public class Calculator { public int Add(int a, int b) => a + b; public int Multiply(int a, int b) => a * b; // New method added }Добавление новых перегрузок:
// Version 1.0.0 public void Log(string message) { } // Version 1.1.0 - MINOR increment public void Log(string message) { } // Original method unchanged public void Log(string message, LogLevel level) { } // New overload addedДобавление необязательных параметров в существующие методы:
// Version 1.0.0 public void SaveFile(string filename) { } // Version 1.1.0 - MINOR increment public void SaveFile(string filename, bool overwrite = false) { } // Optional parameterПримечание.
Это исходное совместимое изменение, но двоичное критическое изменение. Пользователи этой библиотеки должны перекомпилировать ее для правильной работы. Многие библиотеки рассматривают это только при основных изменениях версии, а не при изменениях незначительных версий.
Увеличение версии патча (исправления ошибок с поддержанием обратной совместимости)
Эти изменения устраняют проблемы без добавления новых функций или нарушения существующих функциональных возможностей:
Исправление ошибки в реализации существующего метода:
// Version 1.0.0 - has a bug public int Divide(int a, int b) { return a / b; // Bug: doesn't handle division by zero } // Version 1.0.1 - PATCH increment public int Divide(int a, int b) { if (b == 0) throw new ArgumentException("Cannot divide by zero"); return a / b; // Bug fixed, behavior improved but API unchanged }Улучшения производительности, которые не изменяют API:
// Version 1.0.0 public List<int> SortNumbers(List<int> numbers) { return numbers.OrderBy(x => x).ToList(); // Slower implementation } // Version 1.0.1 - PATCH increment public List<int> SortNumbers(List<int> numbers) { var result = new List<int>(numbers); result.Sort(); // Faster implementation, same API return result; }
Основным принципом является: если существующий код может использовать новую версию без каких-либо изменений, это обновление 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 означает, что производная реализация расширяет реализацию члена базового класса, а не скрывает его. К члену базового класса должен быть применен модификатор 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: {b.MethodOne()}");
Console.WriteLine($"Derived Method One: {d.MethodOne()}");
}
Выходные данные
Base Method One: Method One
Derived Method One: Derived Method One
Модификатор override обрабатывается во время компиляции, и компилятор выдаст ошибку, если не будет найден виртуальный член для переопределения.
Знание рассмотренных приемов и ситуаций, в которых их следует применять, поможет вам значительно упростить переход с одной версии библиотеки на другую.