Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Neste tutorial, você aprenderá o que significa o controle de versão no .NET. Você também aprenderá sobre os fatores a serem considerados ao fazer o controle de versão de sua biblioteca, bem como ao atualizar para uma nova versão de uma biblioteca.
Versão da linguagem
O compilador C# faz parte do SDK do .NET. Por padrão, o compilador escolhe a versão da linguagem C# que corresponde ao TFM escolhido para seu projeto. Se a versão do SDK for maior do que a estrutura escolhida, o compilador poderá usar uma versão de linguagem maior. Você pode alterar o padrão definindo o elemento LangVersion em seu projeto. Você pode aprender como em nosso artigo sobre opções do compilador.
Aviso
Não é recomendável definir o elemento LangVersion como latest. A configuração latest significa que o compilador instalado usa sua versão mais recente. Isso pode mudar conforme o computador, tornando as compilações não confiáveis. Além disso, ele habilita recursos de linguagem que podem exigir recursos de tempo de execução ou biblioteca não incluídos no SDK atual.
Criando bibliotecas
Como um desenvolvedor que criou a bibliotecas .NET para uso público, provavelmente você esteve em situações em que precisa distribuir novas atualizações. Como você realiza esse processo é muito importante, pois você precisa garantir que haja uma transição suave do código existente para a nova versão da biblioteca. Aqui estão Vários aspectos a considerar ao criar uma nova versão:
Controle de Versão Semântico
Controle de versão semântico (SemVer, de forma abreviada) é uma convenção de nomenclatura aplicada a versões de sua biblioteca para indicar eventos com marcos específicos. Idealmente, as informações de versão que você fornece a sua biblioteca devem ajudar os desenvolvedores a determinar a compatibilidade com seus projetos que usam versões mais antigas da mesma biblioteca.
A abordagem mais básica ao SemVer é o formato de 3 componentes MAJOR.MINOR.PATCH, em que:
-
MAJORé incrementado quando você faz alterações em APIs incompatíveis -
MINORé incrementado quando você adiciona funcionalidades de maneira compatível com versões anteriores -
PATCHé incrementado quando você faz correções de bugs compatíveis com versões anteriores
Entender incrementos de versão com exemplos
Para ajudar a esclarecer quando incrementar cada número de versão, aqui estão exemplos concretos:
Incrementos de versão MAJOR (alterações incompatíveis com a API)
Essas alterações exigem que os usuários modifiquem seu código para trabalhar com a nova versão:
Remover uma propriedade ou método público:
// 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! }Alterando assinaturas de método:
// 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 parameterAlterando o comportamento dos métodos existentes de maneiras que quebram as expectativas:
// 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
** Incrementos de versão menor (funcionalidade compatível com versões anteriores)
Essas alterações adicionam novos recursos sem quebrar o código existente:
Adicionar novas propriedades ou métodos públicos:
// 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 }Adicionando novas sobrecargas:
// 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 addedAdicionando parâmetros opcionais aos métodos existentes:
// Version 1.0.0 public void SaveFile(string filename) { } // Version 1.1.0 - MINOR increment public void SaveFile(string filename, bool overwrite = false) { } // Optional parameterObservação
Essa é uma alteração compatível com o código fonte, mas uma alteração que quebra a compatibilidade binária. Os usuários dessa biblioteca devem recompilar para que ela funcione corretamente. Muitas bibliotecas considerariam isso apenas em alterações de versão principais, não em pequenas alterações de versão.
Incrementos de versão PATCH (correções de bug compatíveis com versões anteriores)
Essas alterações corrigem problemas sem adicionar novos recursos ou quebrar a funcionalidade existente:
Corrigindo um bug na implementação de um método existente:
// 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 }Melhorias de desempenho que não alteram a 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; }
O princípio principal é: se o código existente pode usar sua nova versão sem alterações, é uma atualização MINOR ou PATCH. Se o código existente precisar ser modificado para funcionar com sua nova versão, ele será uma atualização PRINCIPAL.
Também há maneiras de especificar outros cenários, como versões de pré-lançamento, ao aplicar informações de versão à biblioteca do .NET.
Compatibilidade com versões anteriores
Conforme você lança novas versões de sua biblioteca, a compatibilidade com versões anteriores provavelmente será uma de suas principais preocupações. Uma nova versão da biblioteca será compatível com a origem de uma versão anterior se o código que depende da versão anterior puder, quando recompilado, trabalhar com a nova versão. Uma nova versão da biblioteca será compatível de forma binária se um aplicativo que dependia da versão anterior puder, sem recompilação, trabalhar com a nova versão.
Aqui estão algumas coisas a serem consideradas ao tentar manter a compatibilidade com versões mais antigas de sua biblioteca:
- Métodos virtuais: quando você torna um método em virtual não virtual na nova versão, significa que projetos que substituem esse método precisarão ser atualizados. Essa é uma alteração muito grande e significativa que é altamente desaconselhável.
- Assinaturas de método: quando atualizar o comportamento de um método exigir que você altere também sua assinatura, você deve criar uma sobrecarga para que o código que chamar esse método ainda funcione. Você sempre pode manipular a assinatura de método antiga para chamar a nova assinatura de método para que a implementação permaneça consistente.
- Atributo obsoleto: você pode usar esse atributo no seu código para especificar classes ou membros da classe que foram preteridos e provavelmente serão removidos em versões futuras. Isso garante que os desenvolvedores que utilizam sua biblioteca estarão melhor preparados para alterações significativas.
- Argumentos de método opcionais: quando você tornar argumentos de método que antes eram opcionais em compulsórios ou alterar seu valor padrão, todo código que não fornece esses argumentos precisará ser atualizado.
Observação
Tornar argumentos compulsórios em opcionais deve ter muito pouco efeito, especialmente se não alterar o comportamento do método.
Quanto mais fácil for para os usuários atualizarem para a nova versão da sua biblioteca, mais provável será que eles atualizem o quanto antes.
Arquivo de Configuração do Aplicativo
Como um desenvolvedor de .NET, há uma chance muito grande de você já ter encontrado o arquivo o app.config na maioria dos tipos de projeto.
Esse arquivo de configuração simples pode fazer muita diferença para melhorar a distribuição de novas atualizações. Em geral, você deve projetar suas bibliotecas de forma que as informações que provavelmente serão alteradas regularmente sejam armazenadas no arquivo app.config. Dessa forma, quando essas informações forem atualizadas, o arquivo de configuração de versões mais antigas só precisa ser substituído pelo novo, sem a necessidade de recompilar a biblioteca.
Consumindo bibliotecas
Como um desenvolvedor que consome bibliotecas .NET criadas por outros desenvolvedores, vocês provavelmente está ciente de que uma nova versão de uma biblioteca pode não ser totalmente compatível com seu projeto e pode acabar precisando atualizar seu código para trabalhar com essas alterações.
Para sua sorte, o ecossistema do C# e do .NET tem recursos e técnicas que permitem facilmente atualizar nosso aplicativo para trabalhar com novas versões das bibliotecas que podem introduzir alterações interruptivas.
Redirecionamento de associação de assembly
Você pode usar o arquivo app.config para atualizar a versão de uma biblioteca que seu aplicativo usa. Adicionando o que é chamado de redirecionamento de associação, você pode usar a nova versão da biblioteca sem precisar recompilar seu aplicativo. O exemplo a seguir mostra como atualizar o arquivo app.config de seu aplicativo para uso com a versão de patch 1.0.1 de ReferencedLibrary em vez da versão 1.0.0 com que ele foi compilado originalmente.
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>
Observação
Essa abordagem só funcionará se a nova versão do ReferencedLibrary for compatível de forma binária com seu aplicativo.
Consulte a seção Compatibilidade com versões anteriores acima para ver as alterações importantes ao determinar a compatibilidade.
novo
Você usa o modificador new para ocultar membros herdados de uma classe base. Essa é uma maneira das classes derivadas responderem a atualizações em classes base.
Veja o exemplo seguinte:
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();
}
Saída
A base method
A derived method
No exemplo acima, você pode ver como DerivedClass oculta o método MyMethod presente em BaseClass.
Isso significa que quando uma classe base na nova versão de uma biblioteca adiciona um membro que já existe em sua classe derivada, você pode simplesmente usar o modificador new no membro de sua classe derivada para ocultar o membro da classe base.
Quando nenhum modificador new é especificado, uma classe derivada ocultará por padrão membros conflitantes em uma classe base e, embora um aviso do compilador seja gerado, o código ainda será compilado. Isso significa que simplesmente adicionar novos membros a uma classe existente torna a nova versão da biblioteca compatível com a origem e de forma binária com o código que depende dela.
substituição
O modificador override significa que uma implementação derivada estende a implementação de um membro da classe base, em vez de ocultá-lo. O membro da classe base precisa ter o modificador virtual aplicado a ele.
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()}");
}
Saída
Base Method One: Method One
Derived Method One: Derived Method One
O modificador override é avaliado em tempo de compilação e o compilador gerará um erro se não encontrar um membro virtual para substituir.
Seu conhecimento sobre as técnicas discutidas e sua compreensão das situações em que usá-las, farão muita diferença para facilitar a transição entre versões de uma biblioteca.