C# Avisos do Compilador
Na aula anterior, os erros impediam o programa de rodar. Os avisos (warnings) são diferentes: o programa roda, mas o compilador encontrou algo com cara de engano e deixou um recado. Uma variável criada e nunca usada, um ; que desliga um if, um valor que pode ser null: o programa compila, e às vezes até funciona no teste, para dar errado depois. Nesta aula você aprende a ler os avisos, conhece os mais comuns de quem começa, descobre por que um aviso "some" sozinho, e vê como transformar todos eles em erros, para nenhum passar despercebido.
- Como ler
arquivo(linha,coluna): warning CS0168: …e o que muda em relação a umerror. - Os avisos mais comuns: variável não usada (
CS0168,CS0219), possívelnull(CS8600,CS8602), código que nunca roda (CS0162),;depois doif(CS0642),=no lugar de==(CS0665) e outros. - Por que o aviso aparece na primeira execução e some na segunda.
- Como transformar avisos em erros:
TreatWarningsAsErrorse-warnaserror. - Como silenciar um aviso de propósito (
#pragma,NoWarn), e por que quase nunca deve.
Os programas desta aula foram rodados na máquina fictícia da Ana com o .NET 10 SDK (C# 14), na pasta ~/cs/avisos com dotnet run arquivo.cs e no projeto ~/projetos/Avisos. As mensagens saem em português; o código CS de cada uma é igual em qualquer língua, e a versão em inglês vem junto para você pesquisar.
A ideia: a luz do painel do carro
No painel do carro há dois tipos de luz. A vermelha (bateria, óleo) quer dizer "pare agora". A amarela (revisão, pneu meio vazio) quer dizer "o carro anda, mas tem algo a conferir". Dá para dirigir semanas com a luz amarela acesa. Até o dia em que o pneu fura na estrada.
No compilador, o error é a luz vermelha: o programa não roda. O warning é a luz amarela: o programa roda, mas algo merece uma olhada. Quem programa há tempo tem uma regra simples: não deixar luz amarela acesa.
1. Anatomia de um aviso
int idade;
int pontos = 10;
Console.WriteLine("Placar do jogo");
$ dotnet run naousada.cs; echo "código de saída: $?" /home/ana/cs/avisos/naousada.cs(1,5): warning CS0168: A variável "idade" está declarada, mas nunca é usada /home/ana/cs/avisos/naousada.cs(2,5): warning CS0219: A variável "pontos" é atribuída, mas seu valor nunca é usado Placar do jogo código de saída: 0
O formato é o mesmo dos erros (aula Lendo Erros do Compilador): arquivo, (linha,coluna), tipo, código e descrição. A diferença está no resto:
- O tipo é
warning("aviso"), e nãoerror. - O programa rodou:
Placar do jogoapareceu logo depois dos avisos. - Não há
Ocorreu uma falha no build., e o código de saída é 0.
- CS0168 · "idade" está declarada, mas nunca é usada
- A Ana criou a variável
idade(aula Variáveis) e não fez nada com ela. A coluna 5 aponta o nome. Em inglês:The variable 'idade' is declared but never used. - CS0219 · "pontos" é atribuída, mas seu valor nunca é usado
- Parecido, mas a
pontosaté recebeu um valor (o 10), que ninguém lê depois. Em inglês:The variable 'pontos' is assigned but its value is never used.
Esses dois são inofensivos? Às vezes. Mas muitas vezes eles denunciam um engano de verdade: a Ana queria mostrar os pontos no placar e esqueceu. Ou escreveu o nome errado na linha que deveria usar a variável.
Missão: rode e leia os dois avisos. A Ana queria mostrar os pontos, e a variável vidas sobrou de uma ideia antiga. Apague a linha da vidas e acrescente uma linha que mostre Pontos: 10 usando a variável pontos (por exemplo, com $"Pontos: {pontos}"). Nenhum aviso pode sobrar.
int pontos = 10;
int vidas;
Console.WriteLine("Placar do jogo");
int pontos = 10;
Console.WriteLine("Placar do jogo");
Console.WriteLine($"Pontos: {pontos}");
2. O aviso que some sozinho
A Ana rodou o mesmo arquivo de novo, sem mudar nada:
$ dotnet run naousada.cs Placar do jogo « cadê os avisos?
Os avisos sumiram, mas o problema continua lá. O motivo: para ganhar tempo, o dotnet guarda o programa já compilado. Se o arquivo não mudou, ele não compila de novo, só roda a versão guardada, e o compilador nem chega a dar os avisos. Num projeto, acontece o mesmo com o dotnet build logo depois de um dotnet run:
$ dotnet run
/home/ana/projetos/Avisos/Program.cs(1,5): warning CS0168: A variável "idade" está declarada, mas nunca é usada [/home/ana/projetos/Avisos/Avisos.csproj]
Placar do jogo
$ dotnet run
Placar do jogo
$ dotnet build
Determinando os projetos a serem restaurados...
Todos os projetos estão atualizados para restauração.
Avisos -> /home/ana/projetos/Avisos/bin/Debug/net10.0/Avisos.dll
Compilação com êxito.
0 Aviso(s) « zero? o aviso continua no código!
0 Erro(s)
Tempo Decorrido 00:00:01.31
Para ver todos os avisos de novo, peça uma compilação do zero com --no-incremental ("não incremental", ou seja, sem aproveitar o que já foi compilado):
$ dotnet build --no-incremental 2>&1 | tail -5
/home/ana/projetos/Avisos/Program.cs(1,5): warning CS0168: A variável "idade" está declarada, mas nunca é usada [/home/ana/projetos/Avisos/Avisos.csproj]
1 Aviso(s)
0 Erro(s)
Tempo Decorrido 00:00:01.61
(O 2>&1 | tail -5 mostra só as 5 últimas linhas.) A lição: leia os avisos na hora em que eles aparecem, e não confie que "rodou sem aviso" na segunda vez. Os editores, como o VS Code, mostram os avisos o tempo todo, sublinhados em amarelo ou verde, e não têm esse problema. No editor do guia, o botão Rodar compila o código a cada clique.
3. Os avisos de null: CS8600 e CS8602
Estes são os avisos que quem começa mais vê, logo no primeiro programa que lê do teclado:
Console.Write("Seu nome: ");
string nome = Console.ReadLine();
Console.WriteLine($"Olá, {nome}! Seu nome tem {nome.Length} letras.");
$ echo Ana | dotnet run nulo.cs /home/ana/cs/avisos/nulo.cs(2,15): warning CS8600: Conversão de literal nula ou possível valor nulo em tipo não anulável. /home/ana/cs/avisos/nulo.cs(3,48): warning CS8602: Desreferência de uma referência possivelmente nula. Seu nome: Olá, Ana! Seu nome tem 3 letras.
(O echo Ana | digita "Ana" para o programa; o Console.ReadLine lê o que foi digitado, aula Ler do Teclado.) Funcionou. Então por que os avisos?
O Console.ReadLine() pode devolver null, que quer dizer "nenhum valor": acontece quando não há mais nada para ler, por exemplo quando a entrada acaba antes de a pessoa digitar alguma coisa. O compilador sabe disso e avisa:
- CS8600 · (2,15)
- "Conversão de possível valor nulo em tipo não anulável": o
stringda linha 2 promete quenomesempre tem um texto, mas oReadLine()pode não ter. Em inglês:Converting null literal or possible null value to non-nullable type. - CS8602 · (3,48)
- "Desreferência de uma referência possivelmente nula": na coluna 48 está o
nome.Length. Pedir o tamanho de um nome que não existe é pedir o tamanho de nada. Em inglês:Dereference of a possibly null reference.
E o compilador tem razão. Sem nenhuma entrada (o < /dev/null faz a entrada acabar na hora), o programa cai:
$ dotnet run nulo.cs < /dev/null; echo "código de saída: $?" Seu nome: Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object. at Program.<Main>$(String[] args) in /home/ana/cs/avisos/nulo.cs:line 3 código de saída: 134
A NullReferenceException ("referência a objeto não definida") é uma das exceções mais famosas do .NET. O aviso CS8602 estava apontando exatamente a linha 3. É para isso que serve a linha <Nullable>enable</Nullable> do .csproj (aula Projetos): ela liga esses avisos, e o guia recomenda deixá-la sempre ligada.
O conserto mais simples
Diga o que usar quando vier null. O operador ?? quer dizer "se for nulo, use isto" (aula Operadores ??, ?. e ??=):
Console.Write("Seu nome: ");
string nome = Console.ReadLine() ?? "";
Console.WriteLine($"Olá, {nome}! Seu nome tem {nome.Length} letras.");
$ echo Ana | dotnet run nulo3.cs Seu nome: Olá, Ana! Seu nome tem 3 letras. $ dotnet run nulo3.cs < /dev/null Seu nome: Olá, ! Seu nome tem 0 letras.
Nenhum aviso, e sem entrada o programa não cai mais: o nome vira um texto vazio, com 0 letras. Trocar só o tipo para string? ("texto ou nulo") não resolve: o CS8600 some, mas o CS8602 continua, porque o nome.Length segue perigoso:
$ echo Ana | dotnet run nulo2.cs « string? nome = Console.ReadLine(); /home/ana/cs/avisos/nulo2.cs(3,48): warning CS8602: Desreferência de uma referência possivelmente nula. Seu nome: Olá, Ana! Seu nome tem 3 letras.
O assunto completo está em null e Tipos Anuláveis e Referências Anuláveis.
4. Avisos que apontam um engano de lógica
Estes avisos são valiosos porque pegam erros que compilam e que você talvez nem percebesse ao testar. (Vários usam o if, que tem aula própria, if e else. Por enquanto, leia if (…) como "se…".)
CS0642: o ; depois do if
int idade = 15;
if (idade >= 18);
{
Console.WriteLine("Pode dirigir.");
}
$ dotnet run pontovirgula.cs /home/ana/cs/avisos/pontovirgula.cs(2,17): warning CS0642: Instrução empty possivelmente incorreta Pode dirigir. « com 15 anos!
"Instrução vazia possivelmente incorreta" (a tradução deixou o empty em inglês). O ; logo depois do if (…) é uma instrução vazia: o if controla esse "nada", e o bloco de baixo roda sempre. Uma menina de 15 anos foi liberada para dirigir. Em inglês: Possible mistaken empty statement. Conserto: apague o ; do fim da linha do if (aula Sintaxe: if e } não levam ;).
Missão: o programa diz que a Ana, com 15 anos, pode dirigir. Leia o aviso CS0642 e conserte com um caractere, para o programa mostrar só Fim., sem avisos.
int idade = 15;
if (idade >= 18);
{
Console.WriteLine("Pode dirigir.");
}
Console.WriteLine("Fim.");
int idade = 15;
if (idade >= 18)
{
Console.WriteLine("Pode dirigir.");
}
Console.WriteLine("Fim.");
CS0665: = no lugar de ==
bool aberto = false;
if (aberto = true)
{
Console.WriteLine("A loja está aberta.");
}
$ dotnet run atrib.cs /home/ana/cs/avisos/atrib.cs(2,5): warning CS0665: Atribuição em expressão condicional é sempre constante. Deseja usar == em vez de = ? /home/ana/cs/avisos/atrib.cs(1,6): warning CS0219: A variável "aberto" é atribuída, mas seu valor nunca é usado A loja está aberta. « mas aberto era false!
Um = só guarda um valor; para comparar, são dois: == (aula Operadores de Comparação). aberto = true guarda true na variável, e o if sempre vê true. O próprio aviso sugere o conserto: "Deseja usar == em vez de =?". Em inglês: Assignment in conditional expression is always constant; did you mean to use == instead of = ?. E o CS0219 veio junto porque o valor guardado na linha 1 nunca chegou a ser lido.
Outros que aparecem cedo
| Código | Exemplo | Mensagem (pt) / em inglês | O que fazer |
|---|---|---|---|
CS0162 | return; e, embaixo, Console.WriteLine("Fim"); | Código inacessível detectado / Unreachable code detected | A linha nunca roda: depois do return, o programa já terminou. Apague-a ou mude a ordem. |
CS1717 | pontos = pontos; | Atribuição feita à mesma variável. Você pretendia atribuir outro elemento? / Assignment made to same variable; did you mean to assign something else? | Provavelmente faltou uma conta (pontos = pontos + 1;) ou é outra variável. |
CS8321 | uma função void Ajuda() que ninguém chama | A função local 'Ajuda' está declarada, mas nunca é usada / The local function 'Ajuda' is declared but never used | Chame-a, ou apague-a (aula Funções Locais). |
CS7022 | instruções soltas e um Main no mesmo programa | O ponto de entrada do programa é o código global… | Uma forma só (aula Program.cs e o Main). |
As saídas reais, para conferir:
$ dotnet run inalcancavel.cs /home/ana/cs/avisos/inalcancavel.cs(3,1): warning CS0162: Código inacessível detectado Início $ dotnet run mesma.cs /home/ana/cs/avisos/mesma.cs(2,1): warning CS1717: Atribuição feita à mesma variável. Você pretendia atribuir outro elemento? 10 $ dotnet run funcao.cs /home/ana/cs/avisos/funcao.cs(3,6): warning CS8321: A função local 'Ajuda' está declarada, mas nunca é usada Olá!
Além dos CS…, do compilador, você vai encontrar avisos que começam com outras letras: CA… e IDE… (dos analisadores de código, que sugerem melhorias e aparecem muito nos editores), SYSLIB… (algo do .NET que ficou obsoleto) e NU… (dos pacotes NuGet). Eles se leem do mesmo jeito: pesquise pelo código.
5. Avisos como erros: TreatWarningsAsErrors
Num projeto grande, um aviso novo some no meio de outros cinquenta. Por isso muitas equipes mandam o compilador tratar todo aviso como erro: assim, ninguém consegue deixar um para trás. Para fazer isso numa compilação só, use -warnaserror ("avisos como erro"):
$ dotnet build -warnaserror --no-incremental; echo "código de saída: $?"
Determinando os projetos a serem restaurados...
Todos os projetos estão atualizados para restauração.
/home/ana/projetos/Avisos/Program.cs(1,5): error CS0168: A variável "idade" está declarada, mas nunca é usada [/home/ana/projetos/Avisos/Avisos.csproj]
Avisos -> /home/ana/projetos/Avisos/bin/Debug/net10.0/Avisos.dll
FALHA da compilação.
/home/ana/projetos/Avisos/Program.cs(1,5): error CS0168: A variável "idade" está declarada, mas nunca é usada [/home/ana/projetos/Avisos/Avisos.csproj]
0 Aviso(s)
1 Erro(s)
Tempo Decorrido 00:00:01.65
código de saída: 1
O mesmo CS0168, agora como error, e a compilação falhou. Para deixar isso ligado sempre, ponha a linha TreatWarningsAsErrors ("tratar avisos como erros") no .csproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
</Project>
$ dotnet run; echo "código de saída: $?" /home/ana/projetos/Avisos/Program.cs(1,5): error CS0168: A variável "idade" está declarada, mas nunca é usada [/home/ana/projetos/Avisos/Avisos.csproj] Ocorreu uma falha no build. Corrija os erros de build e execute novamente. código de saída: 1
Num arquivo solto, a mesma opção vai numa linha #:property no topo (aula Rodar com dotnet run):
#:property TreatWarningsAsErrors=true
int idade;
Console.WriteLine("Placar do jogo");
$ dotnet run estrito.cs /home/ana/cs/avisos/estrito.cs(2,5): error CS0168: A variável "idade" está declarada, mas nunca é usada Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
Ligar o TreatWarningsAsErrors desde o começo é um ótimo hábito: ele obriga a ler cada aviso. Se isso atrapalhar enquanto você experimenta, deixe-o desligado, mas mantenha a regra de terminar cada programa sem nenhum aviso. É o que as missões do guia com a meta "sem avisos" conferem.
6. Silenciar um aviso (com cuidado)
Às vezes você tem certeza de que um aviso não se aplica. Existem duas formas de silenciá-lo. A primeira vale para um trecho do arquivo, entre um #pragma warning disable ("desligar aviso") e um #pragma warning restore ("restaurar"):
#pragma warning disable CS0168
int idade;
#pragma warning restore CS0168
Console.WriteLine("Placar do jogo");
$ dotnet run Placar do jogo
A segunda vale para o projeto inteiro, no .csproj: <NoWarn>$(NoWarn);CS0168</NoWarn> ("sem aviso"). O $(NoWarn); no começo mantém os avisos que o próprio .NET já silencia por padrão e acrescenta o seu.
Os dois jogam a sujeira para baixo do tapete. Neste exemplo, o certo é apagar a variável que não é usada. Use o #pragma só quando tiver um motivo forte, e escreva o motivo num comentário ao lado (aula Comentários). Nunca silencie os avisos de null só para "limpar a tela": eles apontam programas que caem.
Quando dá errado
O que significa: o arquivo não mudou, e o dotnet rodou a versão já compilada, sem compilar (seção 2). O problema continua no código.
Como consertar: leia os avisos na primeira vez. Para vê-los de novo, mude o arquivo ou use dotnet build --no-incremental.
warning CS8600 / CS8602 em todo Console.ReadLine()
O que significa: o ReadLine() pode devolver null, e o código usa o resultado como se ele sempre existisse (seção 3).
Como consertar: string nome = Console.ReadLine() ?? "";. Não desligue o Nullable do .csproj para fazer o aviso sumir.
O que significa: muitas vezes, havia um aviso que explicava tudo: CS0642 (; depois do if), CS0665 (= no lugar de ==), CS0162 (código que nunca roda).
Como consertar: role a saída para cima e leia os avisos. Na dúvida, dotnet build --no-incremental.
TreatWarningsAsErrors, o programa não roda mais
O que significa: era essa a ideia: todo aviso agora é um error.
Como consertar: conserte os avisos, um por um. Se precisar rodar com pressa, desligue a opção (false) temporariamente.
Referência rápida
| Código | Em português | Em inglês | Causa comum |
|---|---|---|---|
CS0168 | A variável "x" está declarada, mas nunca é usada | The variable 'x' is declared but never used | Variável esquecida |
CS0219 | A variável "x" é atribuída, mas seu valor nunca é usado | The variable 'x' is assigned but its value is never used | Valor guardado e nunca lido |
CS8600 | Conversão de literal nula ou possível valor nulo em tipo não anulável. | Converting null literal or possible null value to non-nullable type. | string x = Console.ReadLine(); |
CS8602 | Desreferência de uma referência possivelmente nula. | Dereference of a possibly null reference. | x.Length com x talvez nulo |
CS0162 | Código inacessível detectado | Unreachable code detected | Código depois de return |
CS0642 | Instrução empty possivelmente incorreta | Possible mistaken empty statement | if (…); |
CS0665 | Atribuição em expressão condicional é sempre constante… | Assignment in conditional expression is always constant… | if (x = true) |
CS1717 | Atribuição feita à mesma variável… | Assignment made to same variable… | x = x; |
CS8321 | A função local 'f' está declarada, mas nunca é usada | The local function 'f' is declared but never used | Função que ninguém chama |
| Ferramenta | O que faz |
|---|---|
dotnet build --no-incremental | Compila tudo de novo e mostra todos os avisos |
dotnet build -warnaserror | Trata os avisos como erros nesta compilação |
<TreatWarningsAsErrors>true</TreatWarningsAsErrors> | No .csproj: avisos como erros, sempre |
#:property TreatWarningsAsErrors=true | O mesmo, num arquivo solto |
#pragma warning disable CSxxxx … restore CSxxxx | Silencia um aviso num trecho |
<NoWarn>$(NoWarn);CSxxxx</NoWarn> | Silencia um aviso no projeto todo |
Exercícios
1. O tipo
Que palavra, no lugar de error, indica um aviso na mensagem do compilador?
2. Tudo de novo
Complete a opção que faz o dotnet build compilar tudo do zero e mostrar os avisos de novo:
3. Sem null
Complete o conserto do CS8600, para usar um texto vazio quando não vier nada:
4. O compilador mostrou dois warning e nenhum error. O que acontece com o programa?
5. int idade = 15; if (idade >= 18); { Console.WriteLine("Pode"); }: o que acontece?
6. A Ana rodou dotnet run aviso.cs duas vezes, sem mudar o arquivo. Os avisos só apareceram na primeira. Por quê?
7. Qual é a melhor forma de lidar com warning CS0168: A variável "total" está declarada, mas nunca é usada?
Missão final: zero avisos
Missão: o programa da lojinha funciona, mas tem quatro avisos. Rode e conserte cada um, sem mudar o que o programa mostra: o possível null do ReadLine (use ?? ""), a variável que não é usada (apague-a) e o = que deveria ser ==. Repare que consertar o = resolve dois avisos de uma vez. A caixa já digita Ana para você.
Console.Write("Seu nome: ");
string nome = Console.ReadLine();
int idade;
bool cliente = true;
if (cliente = true)
{
Console.WriteLine($"Olá, {nome}! Bem-vinda de volta.");
}
Console.Write("Seu nome: ");
string nome = Console.ReadLine() ?? "";
bool cliente = true;
if (cliente == true)
{
Console.WriteLine($"Olá, {nome}! Bem-vinda de volta.");
}
Resumo
- Um aviso tem o formato
arquivo(linha,coluna): warning CSxxxx: descrição. O programa roda, mas algo tem cara de engano. - Os mais comuns no começo: variável não usada (
CS0168,CS0219), possívelnull(CS8600,CS8602; conserto:?? ""), código inacessível (CS0162),;depois doif(CS0642),=no lugar de==(CS0665). - O aviso aparece quando o código é compilado. Rodando de novo sem mudar nada, ele não aparece, mas continua lá: use
dotnet build --no-incremental. TreatWarningsAsErrors(ou-warnaserror) transforma avisos em erros, para nenhum passar.#pragma warning disableeNoWarnsilenciam avisos, mas não consertam nada. Use com motivo e comentário.- A regra do guia: todo programa termina sem avisos.