Considerações sobre versão e atualização para os desenvolvedores de C#

A compatibilidade é uma meta importante quando novos recursos são adicionados à linguagem C#. Em quase todos os casos, o código existente pode ser recompilado com uma nova versão do compilador sem nenhum problema. A equipe de runtime do .NET também tem uma meta para garantir a compatibilidade das bibliotecas atualizadas. Em quase todos os casos, quando seu aplicativo é iniciado de um runtime atualizado com bibliotecas atualizadas, o comportamento é exatamente o mesmo que nas versões anteriores.

A versão de linguagem usada para compilar seu aplicativo normalmente corresponde ao TFM (moniker da estrutura de destino) de runtime referenciado em seu projeto. Para obter mais informações sobre como alterar a versão de linguagem padrão, confira o artigo intitulado configurar sua versão de linguagem. Esse comportamento padrão garante a compatibilidade máxima.

Quando alterações interruptivas são introduzidas, elas são classificadas como:

  • Alteração interruptiva binária: uma alteração interruptiva binária causa um comportamento diferente, incluindo possivelmente falha, em seu aplicativo ou biblioteca quando iniciado usando um novo runtime. Você deve recompilar seu aplicativo para incorporar essas alterações. O binário existente não funcionará corretamente.
  • Alteração interruptiva de origem: uma alteração interruptiva de origem altera o significado do código-fonte. Você precisa fazer edições de código-fonte antes de compilar seu aplicativo com a versão mais recente do idioma. O binário existente será executado corretamente com o host e o runtime mais recentes. Observe que, para a sintaxe da linguagem, uma alteração interruptiva de origem também é uma alteração comportamental, conforme definido nas alterações interruptivas do runtime.

Quando uma alteração interruptiva binária afeta seu aplicativo, você precisa recompilar seu aplicativo, mas não precisa editar nenhum código-fonte. Quando uma alteração interruptiva de origem afeta seu aplicativo, o binário existente ainda é executado corretamente em ambientes com o runtime e as bibliotecas atualizados. No entanto, você deve fazer alterações de origem para recompilar com a nova versão do idioma e o runtime. Se uma alteração for interruptiva de origem e interruptiva binária, você deverá recompilar seu aplicativo com a versão mais recente e fazer atualizações de origem.

Devido à meta de evitar alterações interruptivas da equipe de linguagem C# e da equipe de runtime, atualizar seu aplicativo normalmente é uma questão de atualizar o TFM e recompilar o aplicativo. No entanto, para bibliotecas distribuídas publicamente, você deve avaliar cuidadosamente sua política quanto a TFMs com suporte e versões de linguagem com suporte. Você poderá estar criando uma nova biblioteca com recursos encontrados na versão mais recente, e precisará garantir que os aplicativos criados com versões anteriores do compilador possam usá-la. Ou você pode estar atualizando uma biblioteca existente e muitos dos seus usuários podem não ter versões atualizadas ainda.

Como introduzir alterações interruptivas em suas bibliotecas

Ao adotar novos recursos de linguagem na API pública da biblioteca, você deve avaliar se a adoção do recurso introduz uma alteração interruptiva binária ou de origem para os usuários da biblioteca. Todas as alterações na implementação interna que não aparecem nas interfaces public ou protected são compatíveis.

Observação

Se você usar o System.Runtime.CompilerServices.InternalsVisibleToAttribute para habilitar tipos para ver membros internos, os membros internos poderão introduzir alterações interruptivas.

Uma alteração interruptiva binária exige que os usuários recompilem o código para usar a nova versão. Por exemplo, considere este método público:

public double CalculateSquare(double value) => value * value;

Se você adicionar o modificador in ao método, essa será uma alteração interruptiva binária:

public double CalculateSquare(in double value) => value * value;

Os usuários devem recompilar qualquer aplicativo que use o método CalculateSquare para que a nova biblioteca funcione corretamente.

Uma alteração interruptiva de origem exige que os usuários alterem o código antes de recompilarem. Por exemplo, pense neste tipo:

public class Person
{
    public string FirstName { get; }
    public string LastName { get; }

    public Person(string firstName, string lastName) => (FirstName, LastName) = (firstName, lastName);

    // other details omitted
}

Em uma versão mais recente, você gostaria de aproveitar os membros sintetizados gerados para tipos record. Você faz as seguintes alterações:

public record class Person(string FirstName, string LastName);

A alteração anterior requer alterações para qualquer tipo derivado de Person. Todas essas declarações devem adicionar o modificador record às declarações.

Impacto de alterações interruptivas

Ao adicionar uma alteração interruptiva binária à biblioteca, você força todos os projetos que usam sua biblioteca a recompilar. No entanto, nenhum código-fonte nesses projetos precisa ser alterado. Como resultado, o impacto da alteração interruptiva é razoavelmente pequeno para cada projeto.

Ao fazer uma alteração interruptiva de origem em sua biblioteca, você exige que todos os projetos façam alterações de origem para usar sua nova biblioteca. Se a alteração necessária exigir novos recursos de linguagem, você força esses projetos a atualizar para a mesma versão de idioma e TFM que você está usando agora. Você exigiu mais trabalho para seus usuários e, possivelmente, forçou-os a atualizar também.

O impacto de qualquer alteração interruptiva que você faz depende do número de projetos que têm uma dependência em sua biblioteca. Se sua biblioteca for usada internamente por alguns aplicativos, você poderá reagir a todas alterações interruptivas em todos os projetos afetados. No entanto, se sua biblioteca for baixada publicamente, você deverá avaliar o impacto potencial e considerar alternativas:

  • Você pode adicionar novas APIs que são APIs existentes paralelas.
  • Você pode considerar builds paralelos para diferentes TFMs.
  • Você pode considerar a multiplataforma.