1. Introdução
Imagina que o código é uma peça de teatro e as variáveis são os atores. Toda vez que uma variável entra numa nova cena (bloco de código), ela pode estar "viva" (tem valor) ou ser um "fantasma" (null). Só que tem um detalhe: o espectador — o compilador — assiste a peça só pelo roteiro. Ele vê onde os atores ganham papéis (recebem valores) e onde eles somem (viram null). Mas ele não consegue ver a realidade (execução do programa) — só pode imaginar pelo texto do roteiro!
Análise estática de fluxo de dados é o processo onde o compilador checa o código antes de rodar o programa, pra sacar se alguém vai tentar fazer uma cena com "queda de NullReferenceException".
Como funciona
A partir do C# 8.0 e do modo Nullable Reference Types (NRT), o compilador analisa se uma variável pode virar null em algum caminho do código. Se sim — ele já te avisa. Ele não deixa você "usar o nome de um ator que virou fantasma do nada".
2. Como ler os avisos do compilador
Principais categorias de avisos
Quando o modo NRT tá ligado, o compilador fica de olho principalmente nas variáveis de referência (string, User, arrays, objetos). Olha só os tipos de avisos mais comuns:
- Possible null assignment — você tá tentando atribuir um valor que pode ser null pra uma variável que não aceita null.
- Dereference of a possibly null reference — você acessou um membro de um objeto, mas o compilador acha que ele pode ser null.
- Possible null return — o método pode retornar null, mesmo que pela assinatura ele deveria devolver um valor.
- Nullable object must have a value — você usou .Value num tipo nullable sem checar antes.
- Unassigned non-nullable property — você não atribuiu valor pra um campo/propriedade que não aceita null.
Normalmente, na IDE (tipo Rider ou Visual Studio), esses avisos aparecem sublinhados com uma linha ondulada, e se passar o mouse aparece uma explicação.
Exemplo de aviso típico
#nullable enable
string? possibleNull = GetString();
// Opa, o compilador reclama: "Dereference of a possibly null reference"
int length = possibleNull.Length; // Aviso!
O compilador não tem certeza se possibleNull não é null, então acessar .Length pode dar exceção.
Se o método for assim:
string? GetString() { ... }
E você faz:
string nonNullable = GetString();
Vai rolar aviso: possível atribuição insegura de null pra variável non-nullable.
3. O compilador analisa seu código
Exemplo 1: atribuição direta
string? name = null;
Console.WriteLine(name.Length); // Aviso: possível acesso a null
Aqui é na cara: name é null, então tentar acessar a propriedade é perigoso.
Exemplo 2: checando null
string? name = GetUserName();
if (name != null)
{
Console.WriteLine(name.Length); // Suave!
}
else
{
Console.WriteLine("Nome não informado!");
}
O compilador entende que dentro do bloco if (name != null) a variável tá segura. Isso é o flow analysis — análise do fluxo de valores dependendo das condições.
4. O compilador não enxerga "dentro do método"
Ele só olha pra assinatura — se tá escrito que o método retorna string?, ele acredita que pode ser null, mesmo que lá dentro sempre retorne uma string.
Exemplo 3: retorno de valor de método
string? GetMaybeName(bool useName)
{
if (useName)
return "Code Jedi";
else
return null;
}
void PrintLength()
{
string? name = GetMaybeName(false);
Console.WriteLine(name.Length); // Aviso!
}
O compilador vê que GetMaybeName pode retornar null, então pede pra você tomar cuidado.
5. Cenários comuns e avisos
Atribuindo nullable em variável non-nullable
string? maybeUser = GetUser();
string alwaysUser = maybeUser; // aviso: possible null assignment
Pra resolver o aviso:
string alwaysUser = maybeUser ?? "Convidado";
Agora sempre vai ter uma string — ou do método, ou "Convidado".
Esqueceu de checar .HasValue em value-type nullable
int? age = GetAge();
int realAge = age.Value; // aviso: possível acesso a null
Tem que fazer assim:
if (age.HasValue)
Console.WriteLine(age.Value);
Ou assim:
int realAge = age ?? -1;
Propriedades automáticas sem inicialização
class User
{
public string Name { get; set; } // aviso: não inicializado!
}
Soluções:
public string Name { get; set; } = "Sem nome";
ou
public string? Name { get; set; }
6. Casos complicados de análise de fluxo
Análise em loops
string? text = null;
while (text == null)
{
text = Console.ReadLine();
}
Console.WriteLine(text.Length); // tudo certo: o compilador entendeu!
Caminhos diferentes de atribuição
string? name;
if (Random.Shared.Next() % 2 == 0)
name = "Alice";
else
name = null;
Console.WriteLine(name.Length); // aviso: possible null dereference
O compilador analisa todos os caminhos. Se em pelo menos um deles for null — ele avisa.
7. Como "acalmar" o compilador (e quando não fazer isso)
Às vezes você tem certeza que o valor não pode ser null, mas o compilador não acredita:
string? value = GetSomething();
if (value == null)
throw new Exception("Esperado valor!");
Console.WriteLine(value.Length); // Aviso sumiu
Ou você usa o operador !:
string? value = GetSomething();
Console.WriteLine(value!.Length); // Sem avisos, mas perigoso
Isso se chama null-forgiving operator. Ele não checa nada — só fala pro compilador: "relaxa, tá tudo sob controle". Só que se você errar — vai tomar exceção em runtime.
GO TO FULL VERSION