Compartilhar via


Alterar as regras de compatibilidade

Ao longo da história, o .NET tentou manter um alto nível de compatibilidade entre versões e 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 ser divergente daquela do .NET Framework:

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

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

Além da compatibilidade entre as implementações do .NET, os desenvolvedores esperam um alto nível de compatibilidade nas versões de uma determinada implementação do .NET. Em particular, o código escrito para uma versão anterior do .NET Core precisa 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 em que essas APIs foram apresentadas.

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

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

Observação

  • Além de servir como um guia de 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 voltadas a várias implementações e versões do .NET.
  • Para saber mais sobre as categorias de compatibilidade, por exemplo, a compatibilidade com versões anteriores, confira Como as alterações de código podem afetar a compatibilidade.

Modificações no contrato público

Alterações nesta categoria modificam a área de superfície pública de um tipo. A maioria das alterações nesta categoria não é permitida porque viola a compatibilidade com versões anteriores (a capacidade de um aplicativo desenvolvido com a versão anterior de uma API ser executado sem recompilação em uma versão posterior).

Tipos

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

  • REQUER ANÁLISE: adicionar uma nova implementação de interface a um tipo

    Essa é uma alteração aceitável, pois não afeta negativamente os clientes existentes. Quaisquer alterações no tipo precisam funcionar dentro dos limites das alterações aceitáveis ​​definidas aqui para que a nova implementação permaneça aceitável. É necessário ter um 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 em um nível anterior. Um exemplo é a interface ISerializable.

  • REQUER ANÁLISE: introduzir uma nova classe base

    Um tipo poderá ser introduzido em uma hierarquia entre dois tipos existentes se ele não introduzir novos membros abstratosou alterar a semântica ou o comportamento de tipos existentes. Por exemplo, no .NET Framework 2.0, a classe DbConnection tornou-se uma nova classe base para SqlConnection, que antes era derivada diretamente de Component.

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

    O assembly antigo precisa ser marcado com o TypeForwardedToAttribute que aponta para o novo assembly.

  • ✔️ PERMITIDO: alterar um tipo de struct para um tipo readonly struct

    A alteração de um tipo readonly struct para um tipo struct não é permitida.

  • ✔️ PERMITIDO: adicionar a palavra-chave sealed ou abstract a um tipo quando não há construtores accessíveis (públicos ou protegidos)

  • ✔️ PERMITIDO: expandir a visibilidade de um tipo

  • NÃO PERMITIDO: alterar o namespace ou nome de um tipo

  • NÃO PERMITIDO: renomear ou remover um tipo público

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

    Observação

    Em casos raros, .NET pode remover uma API pública. Para saber mais, confira Remoção de API no .NET. Para saber mais sobre a política de suporte do .NET, confira Política de Suporte do .NET.

  • NÃO PERMITIDO: alterar o tipo subjacente de uma enumeração

    Esta é uma alteração significativa de tempo de compilação e comportamental, bem como uma alteração significativa binária que pode tornar os argumentos de atributo inseparáveis.

  • NÃO PERMITIDO: selar um tipo que estava sem selo

  • NÃO PERMITIDO: adicionar uma interface ao conjunto de tipos base de uma interface

    Se uma interface implementar uma interface não implementada anteriormente, todos os tipos que implementaram a versão original da interface serão corrompidos.

  • REQUER ANÁLISE: remover 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: é possível adicionar a implementação de uma interface derivada da interface removida. Por exemplo, você pode remover IDisposable se o tipo ou interface agora implementa IComponent, que implementa IDisposable.

  • NÃO PERMITIDO: alterar um tipo readonly struct para um tipo struct

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

  • NÃO PERMITIDO: alterar um tipo struct para um tipo ref struct e vice-versa

  • NÃO PERMITIDO: reduzir a visibilidade de um tipo

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

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 a um tipo que seja sealed

    No entanto, adicionar um membro abstrato a um tipo que tenha construtores acessíveis (públicos ou protegidos) e não seja sealed 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 é sealed

  • ✔️ PERMITIDO: mover um membro para uma classe superior ao tipo do qual ele foi removido na hierarquia

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

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

  • ✔️ PERMITIDO: adicionar um construtor a uma classe, junto com um construtor sem parâmetros se a classe anteriormente não tinha construtores

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

  • ✔️ PERMITIDO: alterar um membro de abstract para virtual

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

  • ✔️ PERMITIDO: remover readonly 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 ANÁLISE: adicionar um novo campo de instância a um tipo

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

  • NÃO PERMITIDO: renomear ou remover um membro ou parâmetro público

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

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

  • NÃO PERMITIDO: adicionar um membro a uma interface

    Se você fornecer uma implementação, a inclusão de 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 dão suporte a DIMs (membros de interface padrão). Além disso, em alguns cenários, o runtime não pode decidir qual membro de interface padrão deve ser invocado. Por esses motivos, a inclusão de um membro a uma interface existente é considerada uma alteração interruptiva.

  • NÃO PERMITIDO: alterar 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

  • NÃO PERMITIDO: adicionar ou remover a palavra-chave in, out ou ref de um parâmetro

  • NÃO PERMITIDO: renomear um parâmetro (incluindo alterar a capitalização)

    Isso é considerado significativo por dois motivos:

  • NÃO PERMITIDO: alterar de um valor de retorno ref para um valor de retorno ref readonly

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

  • NÃO PERMITIDO: adicionar ou remover abstract de um membro

  • NÃO PERMITIDO: remover 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 significativa, pois o compilador C# tende a emitir instruções callvirt de IL (Intermediate Language) 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 de destino do .NET.

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

    Tornar um método virtual significa que o código do consumidor em geral acabaria chamando-o não virtualmente.

  • NÃO PERMITIDO: transformar um membro virtual em abstrato

    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 nenhuma implementação e precisa ser substituído.

  • NÃO PERMITIDO: adicionando a palavra-chave selada a um membro da interface

    Adicionar sealed a um membro de 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 não seja sealed

  • NÃO PERMITIDO: adicionar ou remover a palavra-chave static de um membro

  • NÃO PERMITIDO: adicionar uma sobrecarga que impede uma sobrecarga existente e define um comportamento diferente

    Isso interrompe 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 será vinculado a essa sobrecarga ao passar um valor Int32. No entanto, se você adicionar uma sobrecarga que aceita um Int32, ao recompilar ou usar associação tardia, o compilador agora se associa à nova sobrecarga. Se resultar em um comportamento diferente, significa que essa é uma alteração significativa.

  • NÃO PERMITIDO: adicionar um construtor a uma classe que anteriormente não tinha construtor sem adicionar o construtor sem parâmetros

  • ❌️ NÃO PERMITIDO: adicionar readonly 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 esse não for o caso, será permitido reduzir a visibilidade de um membro protegido.

    Aumentar a visibilidade de um tipo é permitido.

  • NÃO PERMITIDO: alterar o tipo de um membro

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

  • PROIBIDO: adicionar um campo de instância a um struct que não contém campos não públicos

    Se um struct tiver somente campos públicos ou não tiver nenhum campo, os chamadores poderão declarar locais desse tipo de struct sem chamar o construtor dele ou inicializar primeiro o local como default(T), desde que todos os campos públicos sejam definidos no struct antes do primeiro uso. Adicionar campos públicos ou não públicos ao struct é uma alteração interruptiva de fonte para esses chamadores, pois o compilador agora exigirá que os campos adicionais sejam inicializados.

    Além disso, adicionar campos públicos ou não públicos a um struct sem campos ou somente com campos públicos é uma alteração interruptiva binária para os chamadores que aplicaram [SkipLocalsInit] ao código. Como o compilador não estava ciente desses campos no tempo de compilação, ele poderia emitir IL, que não inicializa totalmente o struct e faz com que ele seja criado com base em dados de pilha não inicializados.

    Se um struct tiver campos não públicos, o compilador já fará a imposição da inicialização por meio do construtor ou de default(T) e a adição de campos de instância não será uma alteração interruptiva.

  • NÃO PERMITIDO: disparar um evento existente quando ele nunca foi disparado antes

Alterações de comportamento

Assemblies

  • ✔️ PERMITIDO: tornar um assembly portátil quando ainda há suporte para as mesmas plataformas

  • NÃO PERMITIDO: alterar o nome de um assembly

  • NÃO PERMITIDO: alterar a chave pública de um assembly

Propriedades, campos, parâmetros e valores retornados

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

    Por exemplo, um método que retorna um tipo de Object pode retornar uma instância String. (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

    Embora o intervalo de valores que podem ser passados ​​para o método ou retornados pelo membro possa ser expandidos, o parâmetro ou o tipo de membro não pode. Por exemplo, enquanto os valores passados ​​para um método podem ser expandidos de 0-124 para 0-255, o tipo de parâmetro não pode ser alterado 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 interrompe os membros substituídos existentes, que não funcionarão corretamente para o intervalo estendido de valores.

  • NÃO PERMITIDO: reduzir o intervalo de valores aceitos de uma propriedade ou parâmetro

  • NÃO PERMITIDO: aumentar o intervalo de valores retornados de uma propriedade, campo, valor de retorno ou parâmetro out

  • NÃO PERMITIDO: alterar os valores retornados de uma propriedade, campo, valor de retorno ou parâmetro out

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

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

    Por esse motivo, a remoção dos valores padrão de parâmetro é 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 existente MyMethod(int a = 1). Se você introduzir uma sobrecarga de MyMethod com dois parâmetros opcionais a e b, você 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). Esse padrão permite que MyMethod() compile.

  • NÃO PERMITIDO: alterar a precisão de um valor retornado numérico

  • REQUER ANÁLISE: uma alteração na análise de entrada e no lançamento de novas exceções (mesmo se o comportamento de análise não for 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 manipulação 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çariam a lançar um CultureNotFoundException em vez de um ArgumentException se a cultura não pudesse ser encontrada. Como CultureNotFoundException deriva de ArgumentException, essa é uma alteração 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

    Exceções irrecuperáveis ​​não devem ser capturadas, mas precisam ser manipuladas por um manipulador de nível alto. Portanto, não se espera que os usuários tenham código que capture essas exceções explícitas. As exceções irrecuperáveis são:

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

    A exceção precisa se aplicar 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 que tem como destino a versão anterior.

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

    Por exemplo, um método Divide que anteriormente apenas manipulou valores positivos e lançou um ArgumentOutOfRangeException pode ser alterado para dar suporte a valores negativos e positivos sem gerar uma exceção.

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

    Os desenvolvedores não devem se basear no texto de 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: remover 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: alterar o valor de um atributo que é observável

  • REQUER ANÁLISE: remover um atributo

    Na maioria dos casos, a remoção de um atributo (como NonSerializedAttribute) é uma alteração significativa.

Suporte a plataforma

  • ✔️ PERMITIDO: dar suporte a uma operação em uma plataforma que não tinha suporte anteriormente

  • NÃO PERMITIDO: não oferecer suporte ou agora exigir um pacote de serviço específico para uma operação que anteriormente tinha suporte em uma plataforma

Alterações na implementação interna

  • REQUER ANÁLISE: alterar a área de superfície de um tipo interno

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

  • REQUER ANÁLISE: alterar a implementação interna de um membro

    Essas mudanças geralmente são permitidas, embora interrompam a reflexão privada. Em alguns casos, em que o código do cliente frequentemente depende da reflexão privada ou em que a alteração introduz efeitos colaterais indesejados, essas 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 essas alterações podem quebrar o código que depende da velocidade atual de uma operação. Isso é particularmente verdadeiro no código que depende do tempo de operações assíncronas. A alteração de desempenho não deve afetar outros comportamentos da API em questão; caso contrário, a alteração será interrompida.

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

    Se a alteração em questão não for categorizada como significativa por algum outro motivo, isso será aceitável. Geralmente, ações precisam ser realizadas, o que pode incluir operações extras ou a adição de novas funcionalidades. Isso quase sempre afetará o desempenho, mas pode ser essencial para que a API em questão funcione conforme o esperado.

  • NÃO PERMITIDO: alterar uma API síncrona para assíncrona (e vice-versa)

Alterações de código

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

  • NÃO PERMITIDO: alterar uma struct para uma classe e vice-versa

  • NÃO PERMITIDO: adicionar a palavra-chave verificado a um bloco de código

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

  • NÃO PERMITIDO: remover parâmetros de um parâmetro

  • NÃO PERMITIDO: alterar a ordem na qual os eventos são disparados

    Os desenvolvedores podem razoavelmente esperar que os eventos sejam disparados 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 em que determinados eventos são chamados

  • NÃO PERMITIDO: adicionar o a um tipo de enumeraçãoFlagsAttribute

Confira também