Entendendo a anulabilidade
Se você é um desenvolvedor .NET, é provável que tenha encontrado o System.NullReferenceException. Isso ocorre em tempo de execução quando a null
é desreferenciada, ou seja, quando uma variável é avaliada em tempo de execução, mas a variável se refere a null
. Essa exceção é, de longe, a exceção mais comum dentro do ecossistema .NET. O criador de , Sir Tony Hoare, refere-se como null
o "erro de bilhões de null
dólares".
No exemplo a seguir, a FooBar
variável é atribuída e null
imediatamente desreferenciada, exibindo assim o problema:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Dereference variable by calling ToString.
// This will throw a NullReferenceException.
_ = fooBar.ToString();
// The FooBar type definition.
record FooBar(int Id, string Name);
O problema se torna muito mais difícil de detetar como desenvolvedor quando seus aplicativos crescem em tamanho e complexidade. Detetar possíveis erros como este é um trabalho para ferramentas, e o compilador C# está aqui para ajudar.
Definição de segurança nula
O termo segurança nula define um conjunto de recursos específicos para tipos anuláveis que ajudam a reduzir o número de ocorrências possíveis NullReferenceException
.
Considerando o exemplo anterior FooBar
, você poderia evitar verificar NullReferenceException
se a fooBar
variável estava null
antes de desreferenciar:
// Declare variable and assign it as null.
FooBar fooBar = null;
// Check for null
if (fooBar is not null)
{
_ = fooBar.ToString();
}
// The FooBar type definition for example.
record FooBar(int Id, string Name);
Para ajudar na identificação de cenários como este, o compilador pode inferir a intenção do seu código e impor o comportamento desejado. No entanto, isso ocorre apenas quando um contexto anulável está habilitado. Antes de discutir o contexto anulável, vamos descrever os possíveis tipos anuláveis.
Tipos anuláveis
Antes do C# 2.0, apenas os tipos de referência eram anuláveis. Tipos de valor como int
ou DateTime
não poderiam ser null
. Se esses tipos forem inicializados sem um valor, eles retornarão ao seu default
valor. No caso de um int
, isto é 0
. Para um DateTime
, é DateTime.MinValue
.
Os tipos de referência instanciados sem valores iniciais funcionam de forma diferente. O default
valor para todos os tipos de referência é null
.
Considere o seguinte trecho em C#:
string first; // first is null
string second = string.Empty // second is not null, instead it's an empty string ""
int third; // third is 0 because int is a value type
DateTime date; // date is DateTime.MinValue
No exemplo anterior:
first
énull
porque o tipostring
de referência foi declarado, mas nenhuma atribuição foi feita.second
é atribuídostring.Empty
quando é declarado. O objeto nunca teve umanull
atribuição.third
é0
apesar de não ter sido atribuído. É umstruct
(tipo de valor) e tem umdefault
valor de0
.date
não foi inicializado, mas seudefault
valor é System.DateTime.MinValue.
A partir do C# 2.0, você pode definir tipos de valores anuláveis usando Nullable<T>
(ou T?
para abreviação). Isso permite que os tipos de valor sejam anuláveis. Considere o seguinte trecho em C#:
int? first; // first is implicitly null (uninitialized)
int? second = null; // second is explicitly null
int? third = default; // third is null as the default value for Nullable<Int32> is null
int? fourth = new(); // fourth is 0, since new calls the nullable constructor
No exemplo anterior:
first
énull
porque o tipo de valor nulo não foi inicializado.second
é atribuídonull
quando é declarado.third
énull
como odefault
valor paraNullable<int>
énull
.fourth
é0
como anew()
expressão chama oNullable<int>
construtor eint
é0
por padrão.
O C# 8.0 introduziu tipos de referência anuláveis, onde você pode expressar sua intenção de que um tipo de referência pode ser null
ou é sempre não-null
. Você pode estar pensando: "Eu pensei que todos os tipos de referência são anuláveis!" Você não está errado, e eles estão. Esse recurso permite que você expresse sua intenção, que o compilador tenta impor. A mesma T?
sintaxe expressa que um tipo de referência deve ser anulável.
Considere o seguinte trecho em C#:
#nullable enable
string first = string.Empty;
string second;
string? third;
Dado o exemplo anterior, o compilador infere sua intenção da seguinte maneira:
first
nuncanull
é como é definitivamente atribuído.second
nunca deve sernull
, mesmo que seja inicialmentenull
. Avaliarsecond
antes de atribuir um valor resulta em um aviso do compilador, pois ele não é inicializado.third
pode sernull
. Por exemplo, pode apontar para umSystem.String
, mas pode apontar paranull
. Qualquer uma destas variações é aceitável. O compilador ajuda você avisando se você cancelar a referênciathird
sem primeiro verificar se ele não é nulo.
Importante
Para usar o recurso de tipos de referência anuláveis como mostrado acima, ele deve estar dentro de um contexto anulável. Isso é detalhado na próxima seção.
Contexto anulável
Contextos anuláveis permitem um controle refinado de como o compilador interpreta variáveis de tipo de referência. Há quatro contextos anuláveis possíveis:
disable
: O compilador se comporta de forma semelhante ao C# 7.3 e anteriores.enable
: O compilador permite todas as análises de referência nulas e todos os recursos de linguagem.warnings
: O compilador executa todas as análises nulas e emite avisos quando o código pode cancelar a referêncianull
.annotations
: O compilador não executa análise nula ou emite avisos quando o código pode cancelar a referêncianull
, mas você ainda pode anotar seu código usando tipos?
de referência anuláveis e operadores de tolerância nula (!
).
Este módulo tem como escopo um ou disable
enable
outros contextos anuláveis. Para obter mais informações, consulte Nullable reference types: Nullable contexts.
Habilitar tipos de referência anuláveis
No arquivo de projeto C# (.csproj), adicione um nó filho <Nullable>
ao <Project>
elemento (ou acrescente a um existente <PropertyGroup>
). Isso aplicará o enable
contexto anulável a todo o projeto.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<!-- Omitted for brevity -->
</Project>
Como alternativa, você pode definir o escopo de contexto nulo para um arquivo C# usando uma diretiva de compilador.
#nullable enable
A diretiva de compilador C# anterior é funcionalmente equivalente à configuração do projeto, mas tem como escopo o arquivo no qual reside. Para obter mais informações, consulte Tipos de referência anuláveis: contextos anuláveis (docs)
Importante
O contexto anulável é habilitado no arquivo .csproj por padrão em todos os modelos de projeto C# começando com .NET 6.0 e superior.
Quando o contexto anulável estiver habilitado, você receberá novos avisos. Considere o exemplo anterior FooBar
, que tem dois avisos quando analisado em um contexto anulável:
A
FooBar fooBar = null;
linha tem um aviso nanull
atribuição: C# Aviso CS8600: Convertendo literal nulo ou possível valor nulo para tipo não anulável.A
_ = fooBar.ToString();
linha também tem um aviso. Desta vez, o compilador está preocupado quefooBar
pode ser nulo: C# Aviso CS8602: Desreferência de uma referência possivelmente nula.
Importante
Não há segurança nula garantida , mesmo que você reaja e elimine todos os avisos. Existem alguns cenários limitados que passarão na análise do compilador, mas resultarão em um tempo de execução NullReferenceException
.
Resumo
Nesta unidade, você aprendeu a habilitar um contexto anulável em C# para ajudar a proteger contra NullReferenceException
o . Na próxima unidade, você aprenderá mais sobre como expressar explicitamente sua intenção em um contexto anulável.