Compartilhar via


Tipos de referência anuláveis

Em um contexto de anulável e alheio, todos os tipos de referência eram nulos. Tipos de referência anuláveis refere-se a um grupo de recursos habilitados em um contexto com reconhecimento de anulável que minimiza a probabilidade de que seu código faça com que o runtime lance System.NullReferenceException. Os tipos de referência anuláveis incluem três recursos que ajudam você a evitar essas exceções, incluindo a capacidade de marcar explicitamente um tipo de referência como anulável:

  • Análise aprimorada do fluxo estático que determina se uma variável pode ser null antes de desreferenciá-la.
  • Atributos que anotam APIs para que a análise de fluxo determine o estado nulo.
  • Anotações variáveis que os desenvolvedores usam para declarar explicitamente o null-state pretendido para uma variável.

O compilador rastreia o estado nulo de cada expressão em seu código no momento da compilação. O estado nulo tem um de três valores:

  • não nulo: a expressão é conhecida como não null.
  • possivelmente nulo: a expressão pode ser null.
  • alheio: o compilador não pode determinar o estado nulo da expressão.

As anotações de variáveis determinam a nulidade de uma variável de tipo de referência:

  • não anulável: se você atribuir um valor null ou uma expressão possivelmente nula à variável, o compilador emitirá um aviso. As variáveis que são não anuláveis têm um estado nulo padrão de não nulo.
  • anulável: você pode atribuir um valor null ou uma expressão possivelmente nula à variável. Quando o estado nulo da variável é possivelmente nulo, o compilador emite um aviso se você desreferenciar a variável. O estado nulo padrão da variável é possivelmente nulo.
  • alheio: você pode atribuir um valor null ou uma expressão possivelmente nula à variável. O compilador não emite avisos quando você desreferencia a variável ou quando atribui uma expressão possivelmente nula à variável.

O estado nulo alheio e a nulidade alheia correspondem ao comportamento anterior à introdução dos tipos de referência anuláveis. Esses valores são úteis durante a migração ou quando seu aplicativo usa uma biblioteca que não habilitou tipos de referência anuláveis.

A análise de estado nulo e as anotações variáveis são desabilitadas por padrão para projetos existentes, o que significa que todos os tipos de referência continuam anuláveis. A partir do .NET 6, eles são habilitados por padrão para novos projetos. Para obter informações sobre como habilitar esses recursos declarando um contexto de anotação anulável, consulte Contextos anuláveis.

O restante deste artigo descreve como essas três áreas de recursos funcionam para gerar avisos quando seu código pode estar dereferenciando um valor null. Desreferenciar uma variável significa acessar um de seus membros usando o operador . (ponto), conforme mostrado no exemplo a seguir:

string message = "Hello, World!";
int length = message.Length; // dereferencing "message"

Quando você desreferencia uma variável cujo valor é null, o runtime gera um System.NullReferenceException.

Da mesma forma, avisos podem ser produzidos quando [] a notação é usada para acessar um membro de um objeto quando o objeto é null:

using System;

public class Collection<T>
{
    private T[] array = new T[100];
    public T this[int index]
    {
        get => array[index];
        set => array[index] = value;
    }
}

public static void Main()
{
    Collection<int> c = default;
    c[10] = 1;    // CS8602: Possible derefence of null
}

Você saberá mais sobre:

  • A análise do estado nulo do compilador: como o compilador determina se uma expressão é não nula ou possivelmente nula.
  • Atributos que são aplicados às APIs que fornecem mais contexto para a análise de estado nulo do compilador.
  • Anotações de variáveis anuláveis que fornecem informações sobre sua intenção com relação às variáveis. As anotações são úteis para que os campos definam o estado nulo padrão no início dos métodos membros.
  • As regras que regem os argumentos de tipo genérico. Novas restrições foram adicionadas porque os parâmetros de tipo podem ser tipos de referência ou tipos de valor. O sufixo ? é implementado de forma diferente para tipos de valor anuláveis e tipos de referência anulável.
  • Contextos nulos ajudam a migrar grandes projetos. Você pode habilitar contextos ou avisos anuláveis em partes do seu aplicativo durante a migração. Depois de resolver mais avisos, você pode habilitar os tipos de referência anuláveis para todo o projeto.

Por fim, você aprenderá as armadilhas conhecidas da análise de estado nulo em tipos struct e matrizes.

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

análise de estado nulo

Quando os tipos de referência anuláveis estão habilitados, a Análise de estado nulo rastreia o estado nulo das referências. Uma expressão é não nulo ou talvez nulo. O compilador determina que uma variável é not-null de duas maneiras:

  1. Foi atribuído à variável um valor que é conhecido como não nulo.
  2. A variável foi verificada em relação a null e não foi modificada desde essa verificação.

Quando os tipos de referência anuláveis não estão habilitados, todas as expressões têm o estado nulo de alheio. O restante da seção descreve o comportamento quando os tipos de referência anuláveis estão habilitados.

Qualquer variável que o compilador não tenha determinado como not-null é considerada maybe-null. A análise fornece avisos em situações em que você pode acidentalmente desreferenciar um valor null. O compilador produz avisos com base no estado nulo.

  • Quando uma variável é não nula, essa variável pode ser desreferenciada com segurança.
  • Quando uma variável é maybe-null, essa variável deve ser verificada para garantir que não seja null antes de ser desreferenciada.

Considere o seguinte exemplo:

string message = null;

// warning: dereference null.
Console.WriteLine($"The length of the message is {message.Length}");

var originalMessage = message;
message = "Hello, World!";

// No warning. Analysis determined "message" is not-null.
Console.WriteLine($"The length of the message is {message.Length}");

// warning!
Console.WriteLine(originalMessage.Length);

No exemplo anterior, o compilador determina que message é maybe-null quando a primeira mensagem é impressa. Não há nenhum aviso para a segunda mensagem. A linha final de código produz um aviso, já que originalMessage pode ser nula. O exemplo a seguir mostra um uso mais prático para percorrer uma árvore de nós até a raiz, processando cada nó durante a passagem:

void FindRoot(Node node, Action<Node> processNode)
{
    for (var current = node; current != null; current = current.Parent)
    {
        processNode(current);
    }
}

O código anterior não gera avisos para desreferenciar a variável current. A análise estática determina que current nunca é desreferenciada quando é maybe-null. A variável current é verificada em relação a null antes de current.Parent ser acessada e antes de passar current para a ação ProcessNode. Os exemplos anteriores mostram como o compilador determina o null-state para variáveis locais quando inicializado, atribuído ou comparado a null.

A análise de estado nulo não rastreia os métodos chamados. Como resultado, os campos inicializados em um método auxiliar comum chamado por todos os construtores geram um aviso com o seguinte modelo:

A propriedade 'name' não anulável deve conter um valor não nulo ao sair do construtor.

Você pode resolver esses avisos de duas maneiras: encadeamento de construtores ou atributos anuláveis no método auxiliar. O código a seguir mostra um exemplo de cada um desses casos. A classe Person usa um construtor comum chamado por todos os outros construtores. A classe Student tem um método auxiliar anotado com o atributo System.Diagnostics.CodeAnalysis.MemberNotNullAttribute:


using System.Diagnostics.CodeAnalysis;

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

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

    public Person() : this("John", "Doe") { }
}

public class Student : Person
{
    public string Major { get; set; }

    public Student(string firstName, string lastName, string major)
        : base(firstName, lastName)
    {
        SetMajor(major);
    }

    public Student(string firstName, string lastName) :
        base(firstName, lastName)
    {
        SetMajor();
    }

    public Student()
    {
        SetMajor();
    }

    [MemberNotNull(nameof(Major))]
    private void SetMajor(string? major = default)
    {
        Major = major ?? "Undeclared";
    }
}

Observação

Vários aprimoramentos na atribuição definitiva e na análise de estado nulo foram adicionados ao C# 10. Ao atualizar para o C# 10, você poderá encontrar menos avisos anuláveis que são falsos positivos. Você pode saber mais sobre as melhorias na especificação de recursos para melhorias de atribuição definitivas.

Análise de estado anulável e os avisos gerados pelo compilador ajudam você a evitar erros de programa ao desreferenciar null. O artigo sobre resolução de avisos anuláveis fornece técnicas para corrigir os avisos mais prováveis de serem vistos em seu código.

Atributos em assinaturas de API

A análise de estado nulo precisa de dicas dos desenvolvedores para entender a semântica das APIs. Algumas APIs fornecem verificações nulas e devem alterar o null-state de uma variável de maybe-null para not-null. Outras APIs retornam expressões que são not-null ou maybe-null, dependendo do null-state dos argumentos de entrada. Por exemplo, considere o seguinte código que exibe uma mensagem em letras maiúsculas:

void PrintMessageUpper(string? message)
{
    if (!IsNull(message))
    {
        Console.WriteLine($"{DateTime.Now}: {message.ToUpper()}");
    }
}

bool IsNull(string? s) => s == null;

Com base na inspeção, qualquer desenvolvedor consideraria esse código seguro e não deveria gerar avisos. Entretanto, o compilador não sabe que IsNull fornece uma verificação nula e emite um aviso para a instrução message.ToUpper(), considerando message como uma variável possivelmente nula. Use o atributo NotNullWhen para corrigir esse aviso:

bool IsNull([NotNullWhen(false)] string? s) => s == null;

Esse atributo informa ao compilador que, se IsNull retornar false, o parâmetro s não será nulo. O compilador altera o estado nulo de message para não nulo dentro do bloco if (!IsNull(message)) {...}. Nenhum aviso é emitido.

Os atributos fornecem informações detalhadas sobre o estado nulo dos argumentos, valores de retorno e membros da instância do objeto usada para invocar um membro. Os detalhes sobre cada atributo podem ser encontrados no artigo de referência de idioma sobre atributos de referência anuláveis. A partir do .NET 5, todas as APIs de runtime do .NET serão anotadas. Você aprimora a análise estática anotando suas APIs para fornecer informações semânticas sobre o null-state dos argumentos e os valores retornados.

Anotações de variáveis anuláveis

A análise de estado nulo fornece uma análise robusta para variáveis locais. O compilador precisa de mais informações de você para variáveis membro. O compilador precisa de mais informações para definir o estado nulo de todos os campos no colchete de abertura de um membro. Qualquer um dos construtores acessíveis pode ser usado para inicializar o objeto. Se um campo membro for definido como null, o compilador deve assumir que seu null-state seja maybe-null no início de cada método.

Você usa anotações que podem declarar se uma variável é um tipo de referência anulável ou um tipo de referência não anulável. Essas anotações fazem instruções importantes sobre o null-state das variáveis:

  • Uma referência não deve ser nula. O estado padrão de uma variável de referência não anulável é não nulo. O compilador impõe regras que garantem que seja seguro desreferenciar essas variáveis sem verificar primeiro que ela não está nula:
    • A variável deve ser inicializada para um valor não nulo.
    • A variável nunca pode receber o valor null. O compilador emite um aviso quando o código atribui uma expressão maybe-null a uma variável que não deve ser nula.
  • Uma referência pode ser nula. O estado padrão de uma variável de referência anulável é maybe-null. O compilador impõe regras para garantir que você verifique corretamente se há uma referência null:
    • A variável só poderá ser desreferenciada quando o compilador puder garantir que o valor não seja null.
    • Essas variáveis podem ser inicializadas com o valor padrão null e podem receber o valor null em outro código.
    • O compilador não emite avisos quando o código atribui uma expressão possivelmente nula a uma variável que pode ser nula.

Qualquer variável de referência não anulável tem um estado nulo padrão de não nulo. Qualquer variável de referência anulável tem o estado inicial nulo de possivelmente nulo.

Um tipo de referência que permite valor nulo é indicado usando a mesma sintaxe que tipos de valor que permitem valor nulo: um ? é acrescentado ao tipo da variável. Por exemplo, a seguinte declaração de variável representa uma variável de cadeia de caracteres que permite valor nulo, name:

string? name;

Quando os tipos de referência anuláveis estiverem habilitados, qualquer variável em que o ? não esteja anexado ao nome do tipo é um tipo de referência não anulável. Isso inclui todas as variáveis de tipo de referência no código existente quando você habilitar esse recurso. No entanto, todas as variáveis locais tipadas implicitamente (declaradas usando var) são tipos de referência anuláveis. Como as seções anteriores mostraram, a análise estática determina o estado nulo das variáveis locais para verificar se elas estão possivelmente nulas antes de desreferenciá-las.

Às vezes, você deve substituir um aviso quando souber que uma variável não é nula, mas o compilador determina que seu null-state é maybe-null. Você usa o operador null-forgiving ! seguindo um nome de variável para forçar o null-state a ser not-null. Por exemplo, se você sabe que a variável name não é null, mas o compilador emite um aviso, é possível escrever o seguinte código para substituir a análise do compilador:

name!.Length;

Os tipos de referência anuláveis e os tipos de valor anuláveis fornecem um conceito semântico semelhante: uma variável pode representar um valor ou objeto, ou essa variável pode ser null. No entanto, tipos de referência anuláveis e tipos de valor anulável são implementados de forma diferente: tipos de valor anuláveis são implementados usando System.Nullable<T> e tipos de referência anuláveis são implementados por atributos lidos pelo compilador. Por exemplo, string? e string são ambos representados pelo mesmo tipo: System.String. No entanto, int? e int são representados por System.Nullable<System.Int32> e System.Int32, respectivamente.

Tipos de referência anuláveis são um recurso de tempo de compilação. Isso significa que é possível que os chamadores ignorem avisos, usar null intencionalmente como um argumento para um método que espera uma referência não anulável. Os autores de biblioteca devem incluir verificações de runtime para valores de argumento nulos. O ArgumentNullException.ThrowIfNull é a opção preferencial para verificar um parâmetro em relação ao nulo no tempo de execução.

Importante

Habilitar anotações anuláveis pode alterar a forma como o Entity Framework Core determina se um membro de dados é necessário. Você pode conferir mais detalhes no artigo sobre Conceitos básicos do Entity Framework Core: Trabalhando com tipos de referência anuláveis.

Genéricos

Os genéricos exigem regras detalhadas para lidar com T? para qualquer parâmetro de tipo T. As regras são necessariamente detalhadas devido ao histórico e à implementação diferente para um tipo de valor anulável e um tipo de referência anulável. Tipos de valor anuláveis são implementados usando o struct System.Nullable<T>. Tipos de referência anuláveis são implementados como anotações de tipo que fornecem regras semânticas para o compilador.

  • Se o argumento de tipo para T for um tipo de referência, T? referencia o tipo de referência anulável correspondente. Por exemplo, se T for um string, então T? será um string?.
  • Se o argumento de tipo for T um tipo de valor, T? faça referência ao mesmo tipo de valor, T. Por exemplo, se T for um int, o T? também será um int.
  • Se o argumento de tipo para T for um tipo de referência anulável, T? referencia o mesmo tipo de referência anulável correspondente. Por exemplo, se T for um string?, então T? será um string?.
  • Se o argumento de tipo para T for um tipo de valor anulável, T? referencia o mesmo tipo de valor anulável correspondente. Por exemplo, se T for um int?, então T? será um int?.

Para valores retornados, T? é equivalente a [MaybeNull]T; para valores de argumento, T? é equivalente a [AllowNull]T. Para obter mais informações, consulte o artigo sobre Atributos para análise de null-state na referência da linguagem.

Você pode especificar um comportamento diferente usando restrições:

  • A restrição class significa que T deve ser um tipo de referência não anulável (por exemplo, string). O compilador produz um aviso se você usar um tipo de referência anulável, como string? para T.
  • A restrição class? significa que T deve ser um tipo de referência, não anulável (string) ou um tipo de referência anulável (por exemplo, string?). Quando o parâmetro de tipo é um tipo de referência anulável, como string?, uma expressão de T? referencia esse mesmo tipo de referência anulável, como string?.
  • A restrição notnull significa que T deve ser um tipo de referência não anulável ou um tipo de valor não anulável. Se você usar um tipo de referência anulável ou um tipo de valor anulável para o parâmetro de tipo, o compilador produzirá um aviso. Além disso, quando T é um tipo de valor, o valor retornado é esse tipo de valor, não o tipo de valor anulável correspondente.

Essas restrições ajudam a fornecer mais informações ao compilador sobre como T é usado. Isso ajuda quando os desenvolvedores escolhem o tipo para T e proporciona uma melhor análise de estado nulo quando uma instância de tipo genérico é usada.

Contextos que permitem valor nulo

Em projetos pequenos, você pode habilitar tipos de referência anuláveis, corrigir avisos e continuar. No entanto, para projetos maiores e soluções de vários projetos, isso pode gerar um grande número de avisos. Você pode usar pragmas para habilitar tipos de referência anuláveis arquivo por arquivo à medida que começar a usar os tipos de referência anuláveis. Os novos recursos que protegem contra o lançamento de um System.NullReferenceException podem ser disruptivos quando ativados em uma base de código existente:

  • Todas as variáveis de referência tipadas explicitamente são interpretadas como tipos de referência não anuláveis.
  • O significado da restrição class em genéricos foi alterado para significar um tipo de referência não anulável.
  • Novos avisos são gerados devido a essas novas regras.

O contexto de anotação anulável determina o comportamento do compilador. Há quatro valores para o contexto de notação anulável:

  • desabilitar: o código é alheio anulável. Desabilitar corresponde ao comportamento anterior à habilitação dos tipos de referência anuláveis, exceto pelo fato de que a nova sintaxe produz avisos em vez de erros.
    • Avisos anuláveis estão desabilitados.
    • Todas as variáveis de tipo de referência são tipos de referência anuláveis.
    • O uso do sufixo ? para declarar um tipo de referência anulável produz um aviso.
    • Você pode usar o operador tolerante a nulo, !, mas ele não tem efeito.
  • enable: o compilador habilita toda a análise de referência nula e todos os recursos de linguagem.
    • Todos os novos avisos anuláveis estão habilitados.
    • Você pode usar o sufixo ? para declarar um tipo de referência anulável.
    • As variáveis de tipo de referência sem o sufixo ? são tipos de referência não anuláveis.
    • O operador de tolerância nula suprime avisos para uma possível desreferência de null.
  • warnings: o compilador executa todas as análises nulas e emite avisos quando o código pode desreferenciar null.
    • Todos os novos avisos anuláveis estão habilitados.
    • O uso do sufixo ? para declarar um tipo de referência anulável produz um aviso.
    • Todas as variáveis de tipo de referência têm permissão para serem nulas. No entanto, os membros têm o null-state de not-null na chave de abertura de todos os métodos, a menos que declarados com o sufixo ?.
    • Você pode usar o operador de tolerância a nulo, !.
  • anotações: o compilador não emite avisos quando o código pode desreferenciar null ou quando você atribui uma expressão possivelmente nula a uma variável não nula.
    • Todos os avisos anuláveis estão desabilitados.
    • Você pode usar o sufixo ? para declarar um tipo de referência anulável.
    • As variáveis de tipo de referência sem o sufixo ? são tipos de referência não anuláveis.
    • Você pode usar o operador tolerante a nulo, !, mas ele não tem efeito.

O contexto de anotação que permite valor nulo e o contexto de aviso que permite valor nulo podem ser definidos para um projeto que usa o elemento <Nullable> em seu arquivo .csproj. Esse elemento configura como o compilador interpreta a nulidade de tipos e quais avisos são gerados. A tabela a seguir mostra os valores permitidos e resume os contextos que eles especificam.

Contexto Avisos de desreferência Avisos de atribuição Tipos de referência Sufixo ? Operador !
disable Desabilitado Desabilitado Todos são anuláveis Produz um aviso Não tem efeito
enable habilitado habilitado Não anulável, a menos que declarado com ? Declara tipo anulável Suprime avisos para uma possível atribuição null
warnings habilitado Não aplicável Todos são anuláveis, mas os membros são considerados não nulos na abertura dos métodos Produz um aviso Suprime avisos para uma possível atribuição null
annotations Desabilitado Desabilitado Não anulável, a menos que declarado com ? Declara tipo anulável Não tem efeito

As variáveis de tipo de referência no código compilado em um contexto disabled são nullable-oblivious. Você pode atribuir um literal null ou uma variável possivelmente nula para uma variável que seja alheia anulável. No entanto, o estado padrão de uma variável nullable-oblivious é not-null.

Você pode escolher qual configuração é melhor para seu projeto:

  • Escolha disable em projetos herdados que você não deseja atualizar com base no diagnóstico ou em novos recursos.
  • Escolha avisos para determinar o local em que seu código poderá gerar System.NullReferenceExceptions. Você pode resolver esses avisos antes de modificar o código para habilitar tipos de referência não anuláveis.
  • Escolha annotations para expressar sua intenção de design antes de habilitar avisos.
  • Escolha enable para novos projetos e projetos ativos em que você deseja proteger contra exceções de referência nulas.

Exemplo:

<Nullable>enable</Nullable>

Também é possível usar diretivas para definir esses mesmos contextos em qualquer lugar no código-fonte: Essas diretivas são mais úteis quando você está migrando uma grande base de código.

  • #nullable enable: define o contexto de anotação que permite valor nulo e o contexto de aviso que permite valor nulo como enable.
  • #nullable disable: define o contexto de anotação que permite valor nulo e o contexto de aviso que permite valor nulo como disable.
  • #nullable restore: restaura o contexto de anotação que permite valor nulo e o contexto de aviso que permite valor nulo para as configurações do projeto.
  • #nullable disable warnings: definir o contexto de aviso anulável para disable.
  • #nullable enable warnings: definir o contexto de aviso anulável para enable.
  • #nullable restore warnings: restaura o contexto de aviso anulável para as configurações do projeto.
  • #nullable disable annotations: definir o contexto de anotação anulável para disable.
  • #nullable enable annotations: definir o contexto de anotação anulável para enable.
  • #nullable restore annotations: Restaura o contexto de anotação anulável para as configurações do projeto.

Para qualquer linha de código, você pode definir qualquer uma das seguintes combinações:

Contexto de aviso Contexto de anotação Use
Padrão do projeto Padrão do projeto Padrão
enable disable Corrigir avisos da análise
enable Padrão do projeto Corrigir avisos da análise
Padrão do projeto enable Adicionar anotações de tipo:
enable enable Código já migrado
disable enable Anotar código antes de corrigir avisos
disable disable Adicionando código herdado ao projeto migrado
Padrão do projeto disable Raramente
disable Padrão do projeto Raramente

Essas nove combinações fornecem controle refinado sobre os diagnósticos que o compilador emite para seu código. Você pode habilitar mais recursos em qualquer área que esteja atualizando, sem ver mais avisos que você ainda não está pronto para resolver.

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.

Por padrão, os contextos de aviso e de anotação de anulável são desabilitados. Isso significa que seu código existente compila sem alterações e sem gerar nenhum aviso novo. A partir do .NET 6, novos projetos incluem o elemento <Nullable>enable</Nullable> em todos os modelos de projeto.

Essas opções fornecem duas estratégias distintas para atualizar uma base de código existente para usar tipos de referência anuláveis.

Armadilhas conhecidas

Matrizes e structs que contêm tipos de referência são armadilhas conhecidas em referências anuláveis e na análise estática que determina a segurança nula. Em ambas as situações, uma referência não anulável pode ser inicializada como null, sem gerar avisos.

Estruturas

Um struct que contém tipos de referência não anuláveis permite atribuir default para ele sem avisos. Considere o seguinte exemplo:

using System;

#nullable enable

public struct Student
{
    public string FirstName;
    public string? MiddleName;
    public string LastName;
}

public static class Program
{
    public static void PrintStudent(Student student)
    {
        Console.WriteLine($"First name: {student.FirstName.ToUpper()}");
        Console.WriteLine($"Middle name: {student.MiddleName?.ToUpper()}");
        Console.WriteLine($"Last name: {student.LastName.ToUpper()}");
    }

    public static void Main() => PrintStudent(default);
}

No exemplo anterior, não há nenhum aviso em PrintStudent(default), enquanto os tipos de referência não anuláveis FirstName e LastName são nulos.

Outro caso mais comum é quando você lida com structs genéricos. Considere o seguinte exemplo:

#nullable enable

public struct S<T>
{
    public T Prop { get; set; }
}

public static class Program
{
    public static void Main()
    {
        string s = default(S<string>).Prop;
    }
}

No exemplo anterior, a propriedade Prop é null em tempo de execução. Ele é atribuído a uma cadeia de caracteres não nula sem nenhum aviso.

matrizes

Matrizes também são uma armadilha conhecida em tipos de referência anuláveis. Considere o exemplo a seguir que não produz avisos:

using System;

#nullable enable

public static class Program
{
    public static void Main()
    {
        string[] values = new string[10];
        string s = values[0];
        Console.WriteLine(s.ToUpper());
    }
}

No exemplo anterior, a declaração da matriz mostra que ela contém cadeias de caracteres não anuláveis, enquanto seus elementos são inicializados para null. Em seguida, a variável s recebe um valor (null o primeiro elemento da matriz). Por fim, a variável s é desreferenciada, causando uma exceção de runtime.

Confira também