Atualizar uma base de código com tipos de referência anuláveis para melhorar os avisos de diagnóstico nulos

Tipos de referência anuláveis permitem declarar se variáveis de um tipo de referência devem ou não ser atribuídas a um valor null. A análise estática e os avisos do compilador quando seu código pode desreferenciar null são o benefício mais importante desse recurso. Depois de habilitado, o compilador gera avisos que ajudam você a evitar a geração de System.NullReferenceException quando o código é executado.

Se a base de código for relativamente pequena, você poderá ativar o recurso em seu projeto, endereçar avisos e aproveitar os benefícios do diagnóstico aprimorado. Bases de código maiores podem exigir uma abordagem mais estruturada para endereçar avisos ao longo do tempo, habilitando o recurso para alguns, conforme você aborda avisos em diferentes tipos ou arquivos. Este artigo descreve diferentes estratégias para atualizar uma base de código e as compensações associadas a essas estratégias. Antes de iniciar a migração, leia a visão geral conceitual dos tipos de referência anuláveis. Ele aborda a análise estática do compilador, valores do estado de nulo iguais a talvez nulo e não nulo e anotações anuláveis. Depois de conhecer esses conceitos e termos, você estará pronto para migrar seu código.

Planeje sua migração

Independentemente de como você atualiza sua base de código, a meta é que avisos anuláveis e anotações anuláveis sejam habilitados em seu projeto. Depois de atingir essa meta, você terá a configuração <nullable>Enable</nullable> em seu projeto. Você não precisará de nenhuma das diretivas de pré-processador para ajustar as configurações em outro lugar.

A primeira opção é definir o padrão do projeto. Suas opções são:

  1. Anulável desabilitado como padrão: Disable será o padrão se você não adicionar um elemento Nullable ao arquivo de projeto. Use esse padrão quando não estiver ativamente adicionando novos arquivos à base de código. A atividade principal é atualizar a biblioteca para usar tipos de referência anuláveis. Usar esse padrão significa que você adiciona uma diretiva de pré-processador anulável a cada arquivo à medida que atualiza o código deles.
  2. Anulável habilitado como padrão: defina esse padrão quando estiver desenvolvendo ativamente novos recursos. Você deseja que todos os novos códigos beneficiem tipos de referência anuláveis e análise estática anulável. Usar esse padrão significa que você deve adicionar #nullable disable à parte superior de cada arquivo. Você removerá essas diretivas de pré-processador ao resolver os avisos em cada arquivo.
  3. Avisos anuláveis como padrão: escolha esse padrão para uma migração em duas fases. Na primeira fase, aborde os avisos. Na segunda fase, ative as anotações para declarar o estado de nulo esperado de uma variável. Usar esse padrão significa que você deve adicionar #nullable disable à parte superior de cada arquivo.
  4. Anotações anuláveis como padrão. Faça anotações no código antes de abordar os avisos.

Habilitar anulável como padrão cria um trabalho mais inicial para adicionar as diretivas de pré-processador a cada arquivo. A vantagem é que cada novo arquivo de código adicionado ao projeto será habilitado para anulável. Qualquer novo trabalho será habilitado para anulável; somente o código existente deve ser atualizado. Desabilitar anulável como o padrão funciona melhor se a biblioteca estiver estável e o foco principal do desenvolvimento for adotar tipos de referência anuláveis. Você ativa tipos de referência anuláveis à medida que anota as APIs. Ao terminar, você habilita os tipos de referência anuláveis para o projeto inteiro. Ao criar um arquivo, você deve adicionar as diretivas de pré-processador e habilitá-lo para anulável. Se algum desenvolvedor em sua equipe esquecer, esse novo código agora estará no backlog do trabalho para tornar todo o código habilitado para anulável.

A sua encolha entre essas estratégias depende da quantidade de desenvolvimento ativo que está ocorrendo em seu projeto. Quanto mais maduro e estável o projeto, melhor será a segunda estratégia. Quanto mais recursos sendo desenvolvidos, melhor será a primeira estratégia.

Importante

O contexto global anulável não se aplica aos arquivos de código gerados. Em qualquer das estratégias, o contexto anulável é desabilitado para todo arquivo de origem marcado como gerado. Isso significa que todas as APIs em arquivos gerados não são anotadas. Há quatro maneiras de um arquivo ser marcado como gerado:

  1. No .editorconfig, especifique generated_code = true em uma seção que se aplica a esse arquivo.
  2. Coloque <auto-generated> ou <auto-generated/> em um comentário na parte superior do arquivo. Ele pode estar em qualquer linha nesse comentário, mas o bloco de comentários deve ser o primeiro elemento do arquivo.
  3. Inicie o nome do arquivo com TemporaryGeneratedFile_
  4. Termine o nome do arquivo com .designer.cs, .generated.cs, .g.cs ou .g.i.cs.

Os geradores podem aceitar usando a diretiva de pré-processador #nullable.

Entender contextos e avisos

Habilitar avisos e anotações controla como o compilador exibe tipos de referência e nulidade. Cada tipo tem uma entre três nulidades:

  • oblivious: todos os tipos de referência são nullable oblivious quando o contexto de anotação está desabilitado.
  • nonnullable: um tipo de referência não anotado, C é nonnullable quando o contexto de anotação está habilitado.
  • nullable: um tipo de referência anotado, C?, é nullable, mas um aviso pode ser emitido quando o contexto de anotação está desabilitado. As variáveis declaradas com var são nullable quando o contexto de anotação está habilitado.

O compilador gera avisos com base nessa nulidade:

  • Tipos nonnullable geram avisos se um valor potencial null é atribuído a eles.
  • Tipos nullable geram avisos se são desreferenciados quando talvez nulos.
  • Tipos oblivious geram avisos se são desreferenciados quando talvez nulo e o contexto de aviso está habilitado.

Cada variável tem um estado anulável padrão que depende de sua nulidade:

  • As variáveis nullable têm um estado de nulo padrão igual a talvez nulo.
  • As variáveis non-nullable têm um estado de nulo padrão igual a não nulo.
  • As variáveis nullable oblivious têm um estado de nulo padrão igual a não nulo.

Antes de habilitar tipos de referência anuláveis, todas as declarações da sua base de código podem ser nullable oblivious. Isso é importante porque significa que todos os tipos de referência têm um estado de nulo padrão igual a não nulo.

Abordar avisos

Se o projeto usa o Entity Framework Core, você deve ler as diretrizes dele sobre Como trabalhar com tipos de referência anuláveis.

Ao iniciar a migração, você deve começar habilitando apenas avisos. Todas as declarações permanecem nullable oblivious, mas você verá avisos quando desreferenciar um valor após o estado de nulo dele mudar para talvez nulo. Ao abordar esses avisos, você verificará nulo em mais locais e sua base de código se tornará mais resiliente. Para aprender técnicas específicas adequadas a diferentes situações, confira o artigo sobre Técnicas para resolver avisos anuláveis.

Você pode abordar avisos e habilitar anotações em cada arquivo ou classe antes de continuar com outro código. No entanto, geralmente é mais eficiente resolver os avisos gerados enquanto o contexto é avisos, antes de habilitar as anotações de tipo. Assim, todos os tipos são oblivious até você abordar o primeiro conjunto de avisos.

Habilitar anotações de tipo

Depois de abordar o primeiro conjunto de avisos, você pode habilitar o contexto de anotação. Isso altera os tipos de referência de oblivious para nonnullable. Todas as variáveis declaradas com var são anuláveis. Essa alteração geralmente introduz novos avisos. A primeira etapa para abordar os avisos do compilador é usar anotações ? em tipos de retorno e parâmetro para indicar quando os argumentos ou os valores retornados podem ser null. Ao realizar essa tarefa, sua meta não é apenas corrigir avisos. A meta mais importante é fazer com que o compilador entenda sua intenção com os possíveis valores nulos.

Atributos estendem anotações de tipo

Vários atributos foram adicionados para expressar informações adicionais sobre o estado de nulo das variáveis. As regras das suas APIs provavelmente são mais complicadas do que não nulas ou talvez nulo para todos os parâmetros e valores retornados. Muitas das suas APIs têm regras mais complexas para quando as variáveis podem ou não ser null. Nesses casos, você usará atributos para expressar essas regras. Os atributos que descrevem a semântica da sua API são encontrados no artigo sobre Atributos que afetam a análise anulável.

Próximas etapas

Depois de resolver todos os avisos após habilitar anotações, você pode definir o contexto padrão do seu projeto como habilitado. Se você adicionou pragmas ao seu código para o contexto de anotações ou avisos anuláveis, você pode removê-los. Com o tempo, você pode ver novos avisos. Você pode escrever um código que introduz avisos. Uma dependência de biblioteca pode ser atualizada para tipos de referência anuláveis. Essas atualizações vão alterar os tipos dessa biblioteca de nullable oblivious para nonnullable ou nullable.

Você também pode explorar esses conceitos no módulo do Learn sobre Segurança de anuláveis em C#.