Compartilhar via


Alterar regras para compatibilidade

Ao longo de seu histórico, 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:

  • Muitos desenvolvedores originalmente desenvolveram ou continuam a desenvolver aplicativos para o .NET Framework. Eles esperam um comportamento consistente entre implementações do .NET.
  • Os projetos de biblioteca do .NET Standard permitem que os desenvolvedores criem bibliotecas direcionadas 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 se comporte de forma idêntica à mesma biblioteca usada em um aplicativo .NET Framework.

Juntamente com a compatibilidade entre implementações do .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 perfeitamente 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 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 seu impacto na compatibilidade do aplicativo. As alterações 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 (❓).

Observação

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

Modificações no contrato público

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

Tipos

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

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

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

  • REQUER JULGAMENTO: Introdução a 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 dos tipos existentes. Por exemplo, no .NET Framework 2.0, a DbConnection classe tornou-se uma nova classe base para SqlConnection, que anteriormente derivava 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

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

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

  • ✔️ PERMITIDO: Expansão da 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, o .NET pode remover uma API pública. Para obter mais informações, consulte a remoção de API no .NET. Para obter informações sobre a política de suporte do .NET, consulte a 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

  • DISALLOWED: Adicionar uma interface ao conjunto de tipos base de uma interface

    Se uma interface passar a implementar uma interface que anteriormente não implementava, todos os tipos que implementaram a versão original da interface ficarão incompatíveis.

  • 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: alterar um tipo readonly struct para um tipo struct

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

  • NÃO PERMITIDO: alterar um tipo struct para um 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 que o tipo seja lacrado

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

  • ✔️ PERMITIDO: Restringir a visibilidade de um membro protegido quando o tipo não tiver construtores acessíveis (públicos ou protegidos) ou se o tipo estiver lacrado

  • ✔️ 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 pulem a substituição ao chamar a base.

  • ✔️ PERMITIDO: Adicionar um construtor a uma classe, juntamente com um construtor sem parâmetros se a classe anteriormente não tivesse 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: Alterando um membro de abstrato para virtual

  • ✔️ PERMITIDO: Mudança de um valor de retorno ref readonly para ref (exceto para métodos ou interfaces 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 JULGAMENTO: 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.

  • REQUER JULGAMENTO: adicionar um membro a uma interface

    Embora seja uma alteração significativa no sentido de que ele eleva sua versão mínima do .NET para o .NET Core 3.0 (C# 8.0), que é quando os DIMs ( membros de interface) padrão foram introduzidos, a adição de um membro estático, não abstrato e não virtual a uma interface é permitido.

    Se você fornecer uma implementação, adicionar um novo membro a uma interface existente não necessariamente resultará em falhas de compilação em assemblies downstream. No entanto, nem todos os idiomas dão suporte a DIMs. Além disso, em alguns cenários, o runtime não pode decidir qual membro de interface padrão invocar. Em alguns cenários, as interfaces são implementadas por ref struct tipos. Como ref struct os tipos não podem ser encaixoados, eles não podem ser convertidos em tipos de interface. Portanto, ref struct os tipos devem fornecer uma implementação implícita para cada membro da interface. Eles não podem usar a implementação padrão fornecida pela interface. Por esses motivos, use o julgamento ao adicionar um membro a uma interface existente.

  • NÃO PERMITIDO: Alteração do valor de uma constante pública ou membro de enumeração

  • NÃO PERMITIDO: Mudar 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 interrupção 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

  • PROIBIDO: Adicionar a palavra-chave virtual a um membro

    Embora isso geralmente não seja uma alteração significativa porque o compilador C# tende a emitir instruções de Linguagem Intermediária (IL) de callvirt 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árias razões:

    • C# não é o único idioma direcionado ao .NET.
    • 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 por meio do operador de propagação ?. null).

    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 deve ser substituído.

  • NÃO PERMITIDO: adicionando a palavra-chave sealed 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 restringir a visibilidade de um membro protegido quando o tipo não tem construtores acessíveis (public ou protected) e o tipo não é sealed. Se esse não for o caso, a redução da visibilidade de um membro protegido será permitida.

    É permitido aumentar a visibilidade de um membro.

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

    O valor retornado 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 Stringou 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 novos campos - sejam eles públicos ou privados - a uma estrutura sem campos ou com apenas campos públicos é uma alteração que quebra a compatibilidade binária para os usuários que aplicaram [SkipLocalsInit] ao seu 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á imporá a inicialização por meio do construtor ou default(T), e adicionar novos campos de instância não será uma alteração significativa.

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

Alterações comportamentais

Assembléias

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

  • PROIBIDO: Alterar o nome de um componente

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

Propriedades, campos, parâmetros e valores retornados

  • ✔️ PERMITIDO: alterando 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 String instância. (No entanto, a assinatura do método não pode ser alterada.)

  • ✔️ PERMITIDO: Aumentando 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 expandido, o parâmetro ou tipo de membro não pode. Por exemplo, embora os valores passados para um método possam ser expandidos de 0 a 124 para 0 a 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

  • PROIBIDO: 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 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â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 MyMethod(int a = 1)existente. Se você introduzir uma sobrecarga de MyMethod com dois parâmetros opcionais a e b, é possível 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() seja compilado.

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

  • REQUER JULGAMENTO: uma alteração na análise de entrada e geração de novas exceções (mesmo que o comportamento de análise não seja 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 lidar com 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, 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

    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 uma nova trajetória de código

    A exceção deve ser aplicada somente a um novo caminho de código executado com novos valores de parâmetro ou estado e que não pode ser executado pelo código existente direcionado à versão anterior.

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

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

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

    Os desenvolvedores não devem confiar no texto das mensagens de erro, que também são alteradas 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: Alterando o valor de um atributo que não é observável

  • Não permitido: Alterar 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 significativa.

Suporte da plataforma

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

  • 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 de implementação internas

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

    Essas alterações geralmente são permitidas, embora interrompa 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 alterações geralmente são permitidas, embora interrompa a reflexão privada. Em alguns casos, em que o código do cliente frequentemente depende de reflexão privada ou em que a alteração introduz efeitos colaterais não intencionais, essas alterações podem não ser permitidas.

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

    A capacidade de modificar o desempenho de uma operação é essencial, mas essas alterações podem interromper 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 nenhum efeito sobre outro comportamento da API em questão; caso contrário, a alteração será interruptiva.

  • ✔️ PERMITIDO: Indiretamente (e muitas vezes negativamente) alterando o desempenho de uma operação

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

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

Alterações de código

  • ✔️ PERMITIDO: Adicionar params a um parâmetro

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

  • NÃO PERMITIDO: adicionar a instrução checked a um bloco de código

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

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

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

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

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

  • NÃO PERMITIDO: Alteração do número de vezes que certos eventos são chamados

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

Consulte também