Alterar regras de compatibilidade

Ao longo de sua história, o .NET tentou manter um alto nível de compatibilidade de versão para versão e entre implementações do .NET. Embora o .NET 5 (e o .NET Core) e versões posteriores possam ser considerados como uma nova tecnologia em comparação com o .NET Framework, dois fatores principais limitam a capacidade dessa implementação do .NET de divergir do .NET Framework:

  • Um grande número de desenvolvedores originalmente desenvolveu ou continua a desenvolver aplicativos .NET Framework. Eles esperam um comportamento consistente em todas as implementações .NET.

  • Os projetos de biblioteca do .NET Standard permitem que os desenvolvedores criem bibliotecas destinadas a APIs comuns compartilhadas pelo .NET Framework e .NET 5 (e .NET Core) e versões posteriores. Os desenvolvedores esperam que uma biblioteca usada em um aplicativo .NET 5 deve se comportar de forma idêntica à mesma biblioteca usada em um aplicativo .NET Framework.

Juntamente com a compatibilidade entre implementações .NET, os desenvolvedores esperam um alto nível de compatibilidade entre versões de uma determinada implementação do .NET. Em particular, o código escrito para uma versão anterior do .NET Core deve ser executado sem problemas no .NET 5 ou em uma versão posterior. Na verdade, muitos desenvolvedores esperam que as novas APIs encontradas em versões recém-lançadas do .NET também sejam compatíveis com as versões de pré-lançamento nas quais essas APIs foram introduzidas.

Este artigo descreve as alterações que afetam a compatibilidade e a maneira como a equipe do .NET avalia cada tipo de alteração. Compreender como a equipe do .NET aborda possíveis alterações de quebra é particularmente útil para desenvolvedores que abrem solicitações pull que modificam o comportamento de APIs .NET existentes.

As seções a seguir descrevem as categorias de alterações feitas nas APIs do .NET e seu impacto na compatibilidade do aplicativo. Mudanças são permitidas (✔️), não permitidas (❌), ou exigem julgamento e uma avaliação de quão previsível, óbvio e consistente era o comportamento anterior (❓).

Nota

  • Além de servir como um guia para como as alterações nas bibliotecas .NET são avaliadas, os desenvolvedores de bibliotecas também podem usar esses critérios para avaliar as alterações em suas bibliotecas que visam várias implementações e versões do .NET.
  • Para obter informações sobre as categorias de compatibilidade, por exemplo, compatibilidade direta e retroativa, consulte Como as alterações de código podem afetar a compatibilidade.

Alterações ao contrato público

As alterações nesta categoria modificam a área de superfície pública de um tipo. A maioria das alterações nesta categoria não são permitidas, uma vez que violam a compatibilidade com versões anteriores (a capacidade de um aplicativo que foi desenvolvido com uma versão anterior de uma API para executar sem recompilação em uma versão posterior).

Tipos

  • ✔️ PERMITIDO: Removendo uma implementação de interface de um tipo quando a interface já está implementada por um tipo base

  • REQUER JULGAMENTO: Adicionando uma nova implementação de interface a um tipo

    Esta é uma alteração aceitável porque não afeta negativamente os clientes existentes. Quaisquer alterações ao tipo devem funcionar dentro dos limites das alterações aceitáveis aqui definidas para que a nova implementação permaneça aceitável. É necessário extremo cuidado ao adicionar interfaces que afetam diretamente a capacidade de um designer ou serializador de gerar código ou dados que não podem ser consumidos no nível inferior. Um exemplo é a ISerializable interface.

  • REQUER JULGAMENTO: Introdução de uma nova classe base

    Um tipo pode ser introduzido em uma hierarquia entre dois tipos existentes se não introduzir novos membros abstratos ou alterar a semântica ou o comportamento de tipos existentes. Por exemplo, no .NET Framework 2.0, a DbConnection classe se tornou uma nova classe base para SqlConnection, que anteriormente derivava diretamente do Component.

  • ✔️ PERMITIDO: Mover um tipo de um assembly para outro

    A montagem antiga deve ser marcada com o TypeForwardedToAttribute que aponta para a nova montagem.

  • ✔️ PERMITIDO: Alterando um tipo struct para um readonly struct tipo

    Não é permitido alterar um readonly struct tipo para um struct tipo.

  • ✔️ PERMITIDO: Adicionar a palavra-chave lacradaou abstrata a um tipo quando não há construtores acessíveis (públicos ou protegidos)

  • ✔️ PERMITIDO: Expandindo a visibilidade de um tipo

  • NÃO permitido: Alterando o namespace ou o nome de um tipo

  • NÃO permitido: Renomeando ou removendo um tipo público

    Isso quebra todo o código que usa o tipo renomeado ou removido.

    Nota

    Em casos raros, o .NET pode remover uma API pública. Para obter mais informações, consulte Remoção de API no .NET. Para obter informações sobre o . Política de suporte da NET, consulte Política de suporte do .NET.

  • DISALLOWED: Alterando o tipo subjacente de uma enumeração

    Esta é uma mudança de quebra comportamental e em tempo de compilação, bem como uma alteração de quebra binária que pode tornar os argumentos de atributos não analisáveis.

  • NÃO permitido: Selagem de um tipo que foi previamente deslacrado

  • NÃO permitido: Adicionando uma interface ao conjunto de tipos básicos de uma interface

    Se uma interface implementa uma interface que não implementava anteriormente, todos os tipos que implementaram a versão original da interface são quebrados.

  • REQUER JULGAMENTO: Removendo uma classe do conjunto de classes base ou uma interface do conjunto de interfaces implementadas

    Há uma exceção à regra para remoção de interface: você pode adicionar a implementação de uma interface que deriva da interface removida. Por exemplo, você pode remover IDisposable se o tipo ou interface agora implementa IComponent, que implementa IDisposable.

  • NÃO permitido: Alterando um readonly struct tipo para um tipo struct

    No entanto, é permitida a alteração de um struct tipo para um readonly struct tipo.

  • NÃO permitido: Alterar um tipo struct para um ref struct tipo e vice-versa

  • NÃO permitido: Reduzindo a visibilidade de um tipo

    No entanto, é permitido aumentar a visibilidade de um tipo.

Membros

  • ✔️ PERMITIDO: Expandir a visibilidade de um membro que não é virtual

  • ✔️ PERMITIDO: Adicionar um membro abstrato a um tipo público que não tenha construtores acessíveis (públicos ou protegidos) ou que o tipo esteja selado

    No entanto, adicionar um membro abstrato a um tipo que tenha construtores acessíveis (públicos ou protegidos) e não sealed seja permitido não é permitido.

  • ✔️ PERMITIDO: Restringir a visibilidade de um membro protegido quando o tipo não tem construtores acessíveis (públicos ou protegidos) ou o tipo está selado

  • ✔️ PERMITIDO: Mover um membro para uma classe mais alta na hierarquia do que o tipo do qual ele foi removido

  • ✔️ PERMITIDO: Adicionar ou remover uma substituição

    A introdução de uma substituição pode fazer com que os consumidores anteriores ignorem a substituição ao chamar a base.

  • ✔️ ALLOWED: Adicionando um construtor a uma classe, juntamente com um construtor sem parâmetros se a classe anteriormente não tinha construtores

    No entanto, adicionar um construtor a uma classe que anteriormente não tinha construtores sem adicionar o construtor sem parâmetros não é permitido.

  • ✔️ PERMITIDO: Alterar um membro de abstrato para virtual

  • ✔️ PERMITIDO: Alterar de um ref readonly para um ref valor de retorno (exceto para métodos virtuais ou interfaces)

  • ✔️ PERMITIDO: Remover somente leitura de um campo, a menos que o tipo estático do campo seja um tipo de valor mutável

  • ✔️ PERMITIDO: Chamar um novo evento que não foi definido anteriormente

  • REQUER JULGAMENTO: Adicionar um novo campo de instância a um tipo

    Essa alteração afeta a serialização.

  • NÃO permitido: Renomeando ou removendo um membro público ou parâmetro

    Isso quebra todo o código que usa o membro renomeado ou removido, ou parâmetro.

    Isso inclui remover ou renomear um getter ou setter de uma propriedade, bem como renomear ou remover membros de enumeração.

  • NÃO permitido: Adicionando um membro a uma interface

    Se você fornecer uma implementação, adicionar um novo membro a uma interface existente não resultará necessariamente em falhas de compilação em assemblies downstream. No entanto, nem todos os idiomas suportam membros de interface padrão (DIMs). Além disso, em alguns cenários, o tempo de execução não pode decidir qual membro da interface padrão invocar. Por esses motivos, adicionar um membro a uma interface existente é considerado uma alteração de rutura.

  • NÃO permitido: Alterando o valor de uma constante pública ou membro de enumeração

  • NÃO permitido: Alterar o tipo de uma propriedade, campo, parâmetro ou valor de retorno

  • NÃO permitido: Adicionar, remover ou alterar a ordem dos parâmetros

  • DISALLOWED: Adicionando ou removendo a palavra-chave in, out ou ref de um parâmetro

  • NÃO permitido: Renomeando um parâmetro (incluindo a alteração de maiúsculas e minúsculas)

    Isso é considerado quebra por dois motivos:

  • NÃO permitido: Alterar de um ref valor de retorno para um ref readonly valor de retorno

  • ️ NÃO permitido: Alterar de um ref readonly para um ref valor de retorno em um método ou interface virtual

  • NÃO permitido: Adicionar ou remover resumo de um membro

  • NÃO permitido: Removendo a palavra-chave virtual de um membro

  • NÃO permitido: Adicionar a palavra-chave virtual a um membro

    Embora isso geralmente não seja uma alteração de quebra porque o compilador C# tende a emitir instruções callvirt Intermediate Language (IL) para chamar métodos não virtuais (callvirt executa uma verificação nula, enquanto uma chamada normal não), esse comportamento não é invariável por vários motivos:

    • C# não é a única linguagem que o .NET destina.

    • O compilador C# tenta cada vez mais otimizar callvirt para uma chamada normal sempre que o método de destino é não-virtual e provavelmente não é nulo (como um método acessado através do operador de propagação nula ?.).

    Tornar um método virtual significa que o código do consumidor muitas vezes acabaria chamando-o de não virtual.

  • NÃO permitido: Fazer um resumo de membro virtual

    Um membro virtual fornece uma implementação de método que pode ser substituída por uma classe derivada. Um membro abstrato não fornece implementação e deve ser substituído.

  • NÃO permitido: Adicionando a palavra-chave lacrada a um membro da interface

    Adicionar sealed a um membro da interface padrão o tornará não virtual, impedindo que a implementação de um tipo derivado desse membro seja chamada.

  • NÃO permitido: Adicionar um membro abstrato a um tipo público que tenha construtores acessíveis (públicos ou protegidos) e que não esteja selado

  • NÃO permitido: Adicionar ou remover a palavra-chave estática de um membro

  • NÃO permitido: Adicionar uma sobrecarga que impede uma sobrecarga existente e define um comportamento diferente

    Isso quebra os clientes existentes que estavam vinculados à sobrecarga anterior. Por exemplo, se uma classe tiver uma única versão de um método que aceite um UInt32, um consumidor existente se associará com êxito a essa sobrecarga ao passar um Int32 valor. No entanto, se você adicionar uma sobrecarga que aceita um Int32, ao recompilar ou usar a ligação tardia, o compilador agora se liga à nova sobrecarga. Se um comportamento diferente resultar, esta é uma mudança de rutura.

  • DISALLOWED: Adicionando um construtor a uma classe que anteriormente não tinha nenhum construtor sem adicionar o construtor sem parâmetros

  • ️ NÃO permitido: Adicionar somente leitura a um campo

  • NÃO permitido: Reduzir a visibilidade de um membro

    Isso inclui reduzir a visibilidade de um membro protegido quando há construtores acessíveis (public ou protected) e o tipo nãoé selado. Se não for esse o caso, é permitida a redução da visibilidade de um membro protegido.

    É permitido aumentar a visibilidade de um membro.

  • NÃO permitido: Alterar o tipo de membro

    O valor de retorno de um método ou o tipo de uma propriedade ou campo não pode ser modificado. Por exemplo, a assinatura de um método que retorna um Object não pode ser alterada para retornar um String, ou vice-versa.

  • DISALLOWED: Adicionando um campo de instância a uma struct que não tem campos não públicos

    Se uma struct tiver apenas campos públicos ou não tiver campos, os chamadores poderão declarar locais desse tipo struct sem chamar o construtor da struct ou primeiro inicializar o local para default(T), desde que todos os campos públicos sejam definidos na struct antes do primeiro uso. Adicionar quaisquer novos campos - públicos ou não públicos - a tal struct é uma alteração de quebra de fonte para esses chamadores, pois o compilador agora exigirá que os campos adicionais sejam inicializados.

    Além disso, adicionar quaisquer novos campos - públicos ou não públicos - a uma estrutura sem campos ou apenas campos públicos é uma alteração de quebra binária para chamadores que se aplicaram [SkipLocalsInit] ao seu código. Como o compilador não estava ciente desses campos em tempo de compilação, ele poderia emitir IL que não inicializa totalmente o struct, levando ao struct sendo criado a partir de dados de pilha não inicializados.

    Se um struct tiver campos não públicos, o compilador já impõe a inicialização por meio do construtor ou default(T), e adicionar novos campos de instância não é uma alteração de quebra.

  • NÃO permitido: Disparar um evento existente quando ele nunca foi disparado antes

Mudanças comportamentais

Assemblagens

  • ✔️ PERMITIDO: Tornar um conjunto portátil quando as mesmas plataformas ainda são suportadas

  • NÃO permitido: Alterando o nome de um assembly

  • NÃO permitido: Alterar a chave pública de um assembly

Propriedades, campos, parâmetros e valores de retorno

  • ✔️ PERMITIDO: Alterar o valor de uma propriedade, campo, valor de retorno ou parâmetro out para um tipo mais derivado

    Por exemplo, um método que retorna um tipo de Object pode retornar uma String instância. (No entanto, a assinatura do método não pode ser alterada.)

  • ✔️ PERMITIDO: Aumentar o intervalo de valores aceitos para uma propriedade ou parâmetro se o membro não for virtual

    Enquanto o intervalo de valores que podem ser passados para o método ou são retornados pelo membro pode expandir, o parâmetro ou tipo de membro não pode. Por exemplo, enquanto os valores passados para um método podem se expandir de 0-124 para 0-255, o tipo de parâmetro não pode mudar de Byte para Int32.

  • NÃO permitido: Aumentar o intervalo de valores aceitos para uma propriedade ou parâmetro se o membro for virtual

    Essa alteração quebra os membros substituídos existentes, que não funcionarão corretamente para o intervalo estendido de valores.

  • NÃO permitido: Diminuindo o intervalo de valores aceitos para uma propriedade ou parâmetro

  • NÃO permitido: Aumentando o intervalo de valores retornados para uma propriedade, campo, valor de retorno ou parâmetro de saída

  • DISALLOWED: Alterando os valores retornados para uma propriedade, campo, valor de retorno de método ou parâmetro out

  • NÃO permitido: alterando o valor padrão de uma propriedade, campo ou parâmetro

    Alterar ou remover um valor padrão de parâmetro não é uma quebra binária. Remover um valor padrão de parâmetro é uma quebra de origem, e alterar um valor padrão de parâmetro pode resultar em uma quebra comportamental após a recompilação.

    Por esse motivo, a remoção de valores padrão de parâmetros é aceitável no caso específico de "mover" esses valores padrão para uma nova sobrecarga de método para eliminar a ambiguidade. Por exemplo, considere um método MyMethod(int a = 1)existente . Se você introduzir uma sobrecarga de MyMethod com dois parâmetros a opcionais e b, poderá preservar a compatibilidade movendo o valor padrão de a para a nova sobrecarga. Agora as duas sobrecargas são MyMethod(int a) e MyMethod(int a = 1, int b = 2). Este padrão permite MyMethod() compilar.

  • NÃO permitido: Alterando a precisão de um valor de retorno numérico

  • REQUER JULGAMENTO: Uma alteração na análise de entrada e lançamento de novas exceções (mesmo que o comportamento de análise não esteja especificado na documentação

Exceções

  • ✔️ PERMITIDO: Lançar uma exceção mais derivada do que uma exceção existente

    Como a nova exceção é uma subclasse de uma exceção existente, o código de tratamento de exceção anterior continua a manipular a exceção. Por exemplo, no .NET Framework 4, os métodos de criação e recuperação de cultura começaram a lançar um CultureNotFoundException em vez de um ArgumentException se a cultura não pudesse ser encontrada. Porque CultureNotFoundException deriva de ArgumentException, esta é uma mudança aceitável.

  • ✔️ PERMITIDO: Lançar uma exceção mais específica do que NotSupportedException, NotImplementedException, NullReferenceException

  • ✔️ PERMITIDO: Lançar uma exceção que é considerada irrecuperável

    As exceções irrecuperáveis não devem ser detetadas, devendo, em vez disso, ser tratadas por um manipulador de alto nível. Portanto, não se espera que os usuários tenham um código que detete essas exceções explícitas. As exceções irrecuperáveis são:

  • ✔️ PERMITIDO: Lançando uma nova exceção em um novo caminho de código

    A exceção deve aplicar-se apenas a um novo caminho de código que é executado com novos valores de parâmetro ou estado e que não pode ser executado pelo código existente destinado à versão anterior.

  • ✔️ PERMITIDO: Removendo uma exceção para permitir um comportamento mais robusto ou novos cenários

    Por exemplo, um Divide método que anteriormente só manipulava valores positivos e lançava um ArgumentOutOfRangeException de outra forma pode ser alterado para suportar valores negativos e positivos sem lançar uma exceção.

  • ✔️ PERMITIDO: Alterar o texto de uma mensagem de erro

    Os desenvolvedores não devem confiar no texto das mensagens de erro, que também mudam com base na cultura do usuário.

  • NÃO permitido: Lançar uma exceção em qualquer outro caso não listado acima

  • NÃO permitido: Remoção de uma exceção em qualquer outro caso não listado acima

Atributos

  • ✔️ PERMITIDO: Alterar o valor de um atributo que não é observável

  • NÃO permitido: Alterando o valor de um atributo que é observável

  • REQUER JULGAMENTO: Removendo um atributo

    Na maioria dos casos, remover um atributo (como NonSerializedAttribute) é uma alteração de quebra.

Suporte da plataforma

  • ✔️ PERMITIDO: Suporte a uma operação em uma plataforma que anteriormente não era suportada

  • NÃO permitido: Não suporta ou agora requer um service pack específico para uma operação que anteriormente era suportada em uma plataforma

Alterações à implementação interna

  • REQUER JULGAMENTO: Alterar a área de superfície de um tipo interno

    Tais mudanças são geralmente permitidas, embora quebrem a reflexão privada. Em alguns casos, quando bibliotecas populares de terceiros ou um grande número de desenvolvedores dependem das APIs internas, essas alterações podem não ser permitidas.

  • REQUER JULGAMENTO: Alterar a implementação interna de um membro

    Estas alterações são geralmente permitidas, embora quebrem a reflexão privada. Em alguns casos, quando o código do cliente depende frequentemente de uma reflexão privada ou quando a alteração introduz efeitos secundários não intencionais, estas alterações podem não ser permitidas.

  • ✔️ PERMITIDO: Melhorar o desempenho de uma operação

    A capacidade de modificar o desempenho de uma operação é essencial, mas tais alterações podem quebrar o código que depende da velocidade atual de uma operação. Isso é particularmente verdadeiro para o código que depende do tempo das operações assíncronas. A alteração de desempenho não deve ter efeito sobre outro comportamento da API em questão; caso contrário, a mudança será quebrada.

  • ✔️ PERMITIDO: Alterar indiretamente (e muitas vezes adversamente) o desempenho de uma operação

    Se a mudança em questão não for categorizada como quebra por algum outro motivo, isso é aceitável. Muitas vezes, ações precisam ser tomadas que podem incluir operações extras ou que adicionam novas funcionalidades. Isso quase sempre afetará o desempenho, mas pode ser essencial para fazer a API em questão funcionar conforme o esperado.

  • DISALLOWED: Alterando uma API síncrona para assíncrona (e vice-versa)

Alterações do código

  • ✔️ PERMITIDO: Adicionando parâmetros a um parâmetro

  • NÃO permitido: Alterando uma struct para uma classe e vice-versa

  • NÃO permitido: Adicionar a palavra-chave verificada a um bloco de código

    Essa alteração pode fazer com que o código executado anteriormente lance um OverflowException e é inaceitável.

  • NÃO permitido: Removendo parâmetros de um parâmetro

  • NÃO permitido: Alterando a ordem em que os eventos são disparados

    Os desenvolvedores podem razoavelmente esperar que os eventos sejam acionados na mesma ordem, e o código do desenvolvedor frequentemente depende da ordem em que os eventos são disparados.

  • NÃO permitido: Remover o aumento de um evento em uma determinada ação

  • NÃO permitido: Alterar o número de vezes que determinados eventos são chamados

  • DISALLOWED: Adicionando o FlagsAttribute a um tipo de enumeração

Consulte também