C# Program.cs e o Main
O Program.cs que o dotnet new console criou tem uma linha só: Console.WriteLine("Hello, World!");. Mas abra um livro de C#, um tutorial de alguns anos atrás ou o código de uma empresa, e o mesmo "Olá, mundo" aparece com onze linhas: using, namespace, class Program, static void Main(string[] args) e um monte de chaves. Nesta aula você vai ver que os dois são o mesmo programa, entender o que cada linha da forma clássica faz, descobrir o que o compilador escreve escondido na forma curta e saber quando vai encontrar cada uma.
- O que é o ponto de entrada de um programa.
- A forma clássica, com
class Programestatic void Main(string[] args), linha por linha. - A forma curta, com instruções de nível superior C# 9, e o que o compilador gera a partir dela.
- Como as duas formas recebem os argumentos (
args) e devolvem o código de saída. - Como gerar um projeto na forma clássica com
--use-program-main. - As regras da forma curta: um arquivo só, e antes das classes.
- Quando cada forma aparece, e qual usar.
- Os erros
CS5001,CS8803,CS8802,CS0260e o avisoCS7022.
Os programas desta aula foram rodados na máquina fictícia da Ana com o .NET 10 SDK (C# 14), na pasta ~/cs com dotnet run arquivo.cs e na pasta ~/projetos com projetos. As saídas foram copiadas da tela.
A ideia: a porta de entrada
Um prédio grande tem muitas portas, mas só uma é a entrada principal, com a placa "Entrada". Quem chega sabe por onde começar. Uma casinha de um cômodo só não precisa de placa: você abre a porta e já está lá dentro.
Um programa também precisa de uma entrada: o lugar onde ele começa a rodar. Isso se chama ponto de entrada (em inglês, entry point). Em C#, o ponto de entrada é um método (um bloco de código com nome) chamado Main, "principal".
- Na forma clássica, você escreve a placa: monta a classe
Programe o métodoMaincom todas as letras. - Na forma curta, você escreve só o que o programa faz, direto no arquivo, e o compilador põe a placa sozinho.
1. A forma clássica: static void Main(string[] args)
Este é o programa da Ana escrito como nos livros. Ele cumprimenta e conta quantos argumentos recebeu (os argumentos são as palavras escritas depois do nome do programa; aula Argumentos da Linha de Comando):
using System;
namespace Saudacao
{
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Olá, Ana!");
Console.WriteLine($"Você passou {args.Length} argumento(s).");
}
}
}
$ dotnet run antigo.cs Olá, Ana! Você passou 0 argumento(s). $ dotnet run antigo.cs -- maçã banana Olá, Ana! Você passou 2 argumento(s). « o -- separa as opções do dotnet dos argumentos do programa
São muitas palavras novas de uma vez. Você não precisa dominar nenhuma delas agora: cada uma tem a sua aula. Por enquanto, basta saber o papel de cada linha:
- using System;
- "Usando o
System": deixa você escreverConsoleem vez do nome completo,System.Console. Nos projetos novos, essa linha vem implícita (oImplicitUsingsdo.csproj, aula Projetos), por isso pode ser omitida. Aula Namespaces e using. - namespace Saudacao
- Um namespace ("espaço de nomes") é como o sobrenome de uma família: agrupa as classes e evita confusão entre dois
Programde projetos diferentes. É opcional. - { … }
- As chaves marcam o começo e o fim de cada bloco: o do namespace, o da classe e o do método. Cada
{tem a sua}(aula Sintaxe). - internal class Program
- Uma classe chamada
Program. Em C#, todo método precisa morar dentro de uma classe. Ointernal("interno": visível só dentro deste programa) é opcional, porque já é o padrão. Classes são do Nível 2: Classes e Objetos e public, private e internal. - static
- "Estático": o método pertence à classe, e não a um objeto criado a partir dela. O .NET chama o
Mainantes de existir qualquer objeto, por isso ele precisa serstatic(aula static). - void
- "Vazio": o método não devolve nenhum valor (aula Valor de Retorno). Logo abaixo você vê a versão que devolve um número.
- Main
- O nome que o .NET procura para começar o programa. Com M maiúsculo:
mainnão serve (veja em Quando dá errado). - (string[] args)
- O parâmetro do método:
argsé uma lista (um array) de textos (string) com os argumentos da linha de comando. Aulas Parâmetros e Arrays. - Console.WriteLine(…);
- As instruções que o programa realmente executa, de cima para baixo. É só aqui que está o "trabalho".
Repare na proporção: de 13 linhas, só duas fazem alguma coisa. As outras onze são a "embalagem" que o C# exigia até 2020.
2. A forma curta: instruções de nível superior C# 9
Desde o C# 9 (novembro de 2020), você pode escrever as instruções direto no arquivo, sem classe e sem Main. Elas se chamam instruções de nível superior (em inglês, top-level statements): ficam no "nível de cima" do arquivo, fora de qualquer bloco.
Console.WriteLine("Olá, Ana!");
Console.WriteLine($"Você passou {args.Length} argumento(s).");
$ dotnet run novo.cs Olá, Ana! Você passou 0 argumento(s). $ dotnet run novo.cs -- maçã banana Olá, Ana! Você passou 2 argumento(s).
A mesma saída, nas duas execuções. Repare que o args continua existindo, mesmo sem ninguém ter escrito string[] args: o compilador o oferece de presente para as instruções de nível superior.
O que o compilador faz por você
A forma curta não é outra linguagem. Ao ver instruções soltas no arquivo, o compilador escreve sozinho a embalagem: uma classe chamada Program e um método de entrada com um nome especial, <Main>$, com args e tudo. As suas linhas vão para dentro desse método.
Dá para ver esse nome escondido quando o programa cai com um erro. Este programa tenta ler a posição 5 de uma lista que só tem duas notas:
int[] notas = { 9, 8 };
Console.WriteLine(notas[5]);
$ dotnet run excecao.cs; echo "código de saída: $?" Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Program.<Main>$(String[] args) in /home/ana/cs/excecao.cs:line 2 código de saída: 134
A segunda linha diz onde o erro aconteceu: em Program.<Main>$(String[] args), ou seja, no método <Main>$ da classe Program, que você não escreveu. (A mensagem em si, "índice fora dos limites do array", é do .NET e sai sempre em inglês; as exceções têm aula própria, Exceções.) Com a forma clássica, a mesma linha mostra o nome que você escreveu:
$ dotnet run excecao2.cs Unhandled exception. System.IndexOutOfRangeException: Index was outside the bounds of the array. at Notas.Program.Main() in /home/ana/cs/excecao2.cs:line 8
Aqui o excecao2.cs tem namespace Notas, class Program e static void Main(), e o erro está na linha 8. O < e o $ no nome gerado são de propósito: nenhum programador consegue escrever um nome assim, então ele nunca vai bater com um nome seu.
Lado a lado
| Na forma clássica você escreve… | Na forma curta, quem cuida é… |
|---|---|
using System; | o ImplicitUsings (os using implícitos) |
namespace Saudacao { … } | ninguém: o código fica sem namespace, o que é permitido |
class Program { … } | o compilador, que cria a classe Program |
static void Main(string[] args) { … } | o compilador, que cria o método <Main>$ com o args |
as instruções dentro do Main | você: são as únicas linhas do arquivo |
Missão: este programa está na forma clássica. Reescreva-o na forma curta: apague o using, o namespace, a classe, o Main e todas as chaves, e deixe só as duas linhas de Console.WriteLine, encostadas na margem esquerda. A saída tem de continuar igual, sem avisos.
using System;
namespace Lojinha
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Olá, Ana!");
Console.WriteLine("Bem-vinda à lojinha.");
}
}
}
Console.WriteLine("Olá, Ana!");
Console.WriteLine("Bem-vinda à lojinha.");
3. O código de saída: return e int Main
Todo programa, ao terminar, devolve um número ao sistema: o código de saída. 0 quer dizer "deu tudo certo"; outro número quer dizer "algo deu errado" (o terminal mostra o último com echo $?). Até agora ele foi sempre 0. Na forma curta, um return com um número escolhe o código:
Console.WriteLine("Saindo com 3");
return 3;
$ dotnet run sai.cs; echo "código de saída: $?" Saindo com 3 código de saída: 3
Na forma clássica, o Main precisa anunciar que devolve um número: troca-se o void por int (número inteiro). E o args pode ser deixado de fora, se o programa não usa argumentos:
class Program
{
static int Main()
{
System.Console.WriteLine("Sem args e com código de saída");
return 7;
}
}
$ dotnet run intmain.cs; echo "código de saída: $?" Sem args e com código de saída código de saída: 7
(Sem using System;, o arquivo usa o nome completo, System.Console. Funcionaria também só com Console, por causa dos using implícitos.) As formas aceitas para o Main:
| Assinatura | Recebe os argumentos? | Devolve o código de saída? |
|---|---|---|
static void Main() | Não | Não (sempre 0) |
static void Main(string[] args) | Sim | Não (sempre 0) |
static int Main() | Não | Sim, com return |
static int Main(string[] args) | Sim | Sim, com return |
static async Task Main(…) e static async Task<int> Main(…) | Opcional | Só a versão com Task<int>; aula async e await |
Na forma curta, você não escolhe nada disso: o compilador olha o seu código e gera o Main certo. Ele sempre recebe o args (repare no (String[] args) do erro da seção 2, num programa que nem usava argumentos); se você escreveu return 3;, ele passa a devolver int.
4. Gerar um projeto na forma clássica: --use-program-main
O modelo console gera a forma curta. Para ter a clássica, use a opção --use-program-main ("usar o Main do Program"):
$ dotnet new console -o Antigo --use-program-main
O modelo "Aplicativo do Console" foi criado com êxito.
Processando ações pós-criação...
Restaurando /home/ana/projetos/Antigo/Antigo.csproj:
Determinando os projetos a serem restaurados...
/home/ana/projetos/Antigo/Antigo.csproj restaurado (em 104 ms).
A restauração foi bem-sucedida.
$ cat Antigo/Program.cs
namespace Antigo;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, World!");
}
}
$ cd Antigo && dotnet run
Hello, World!
É a forma clássica, mas um pouco enxugada pelo C# moderno:
- Sem
using System;: ele é implícito nos projetos novos. namespace Antigo;, com ponto e vírgula e sem chaves: é o namespace de arquivo C# 10, que vale para o arquivo inteiro e economiza um nível de recuo. O nome é o do projeto.- Sem
internal, que já é o padrão.
5. As regras da forma curta
As instruções de nível superior são práticas, mas têm duas regras.
Só um arquivo pode ter instruções soltas
Um programa tem uma entrada só. Num projeto com vários .cs, apenas um deles pode ter instruções de nível superior. Se a Ana criar um Outro.cs com um Console.WriteLine solto ao lado do Program.cs, o projeto para de compilar:
$ dotnet build 2>&1 | grep -m1 error /home/ana/projetos/Ola/Program.cs(1,1): error CS8802: Apenas uma unidade de compilação pode ter instruções de nível superior. [/home/ana/projetos/Ola/Ola.csproj]
Unidade de compilação é o nome técnico de "um arquivo .cs". (O 2>&1 | grep -m1 error só filtra a primeira linha com error, para a saída caber aqui.) Os outros arquivos de um projeto guardam classes e métodos, como você vai fazer no Nível 2.
Primeiro as instruções, depois as classes
No mesmo arquivo das instruções soltas, você pode criar funções (métodos sem classe, que o compilador põe dentro do Main gerado; aula Funções Locais) e classes. Mas as classes têm de vir depois das instruções:
Console.WriteLine("Lojinha da Ana");
MostrarPreco("Caderno", 12.50m);
MostrarPreco("Caneta", 3.20m);
void MostrarPreco(string produto, decimal preco)
{
Console.WriteLine($"{produto}: {preco:C}");
}
class Ajudante
{
}
$ dotnet run completo.cs Lojinha da Ana Caderno: R$ 12,50 Caneta: R$ 3,20
(Não se preocupe com o void MostrarPreco, o 12.50m e o {preco:C}: são métodos, decimal e formatação de dinheiro, cada um com a sua aula. O que importa aqui é a ordem: instruções, depois a função, e a classe no fim.) Com a classe antes das instruções, o compilador recusa:
class Ajudante
{
}
Console.WriteLine("Olá!");
$ dotnet run ordem.cs /home/ana/cs/ordem.cs(5,1): error CS8803: As instruções de nível superior precisam preceder as declarações de namespace e de tipo. Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
"Precisam preceder" = "precisam vir antes". Declarações de tipo são as classes (e seus primos: struct, record, enum, interface).
Antes do C# 9, a forma curta não existia
Num projeto configurado para uma versão antiga da linguagem (com <LangVersion>8</LangVersion> no .csproj), a mesma linha solta é recusada, com uma mensagem que diz exatamente qual versão é preciso:
$ dotnet build 2>&1 | grep error | sort -u /home/ana/projetos/Velho/Program.cs(1,1): error CS8400: O recurso 'instruções de nível superior' não está disponível em C# 8.0. Use a versão de linguagem 9.0 ou superior. [/home/ana/projetos/Velho/Velho.csproj]
É por isso que todo o código escrito antes de 2020 usa o Main. Você não precisa mexer no LangVersion: o .NET 10 usa o C# 14, que tem tudo.
Quando cada forma aparece, e qual usar
| Você vai ver a forma… | Onde |
|---|---|
| curta (instruções de nível superior) | No modelo dotnet new console desde o .NET 6 (novembro de 2021); nos arquivos soltos do dotnet run arquivo.cs; na documentação atual da Microsoft; em todas as aulas deste guia. |
clássica (static void Main) | Em livros, cursos e respostas na internet anteriores a 2021; em projetos antigos (.NET Framework, .NET Core 3.1, .NET 5); em empresas que preferem tudo explícito; com --use-program-main. |
Qual usar: para programas novos, a forma curta. Ela é a padrão, tem menos coisas para errar e deixa à vista só o que o programa faz. Mas aprenda a ler a forma clássica, porque você vai encontrá-la o tempo todo. Quando copiar um exemplo antigo, existem dois caminhos: rodar como está (as duas formas funcionam no .NET 10) ou tirar as linhas de dentro do Main e jogar fora a embalagem, como na missão acima. O que não dá é misturar as duas no mesmo programa.
Quando dá errado
error CS5001: Programa não contém um método "Main" estático adequado para um ponto de entrada
O que significa: o compilador procurou a porta de entrada e não achou. Repare que a mensagem não tem arquivo(linha,coluna): o problema não está numa linha, está na falta de alguma coisa. Este minusculo.cs tem static void main, com m minúsculo:
$ dotnet run minusculo.cs error CS5001: Programa não contém um método "Main" estático adequado para um ponto de entrada Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
O C# diferencia maiúsculas de minúsculas: main e Main são nomes diferentes. O mesmo erro aparece quando falta o static (void Main(string[] args)) ou quando o arquivo só tem classes, sem nenhuma instrução solta. Em inglês: Program does not contain a static 'Main' method suitable for an entry point.
Como consertar: escreva static void Main(string[] args) exatamente assim, ou use a forma curta.
error CS8803: As instruções de nível superior precisam preceder as declarações de namespace e de tipo.
O que significa: há uma instrução solta depois de uma classe ou de um namespace (seção 5). Em inglês: Top-level statements must precede namespace and type declarations.
Como consertar: mova as instruções para o topo do arquivo e deixe as classes no fim.
error CS8802: Apenas uma unidade de compilação pode ter instruções de nível superior.
O que significa: dois arquivos do mesmo projeto têm instruções soltas (seção 5). Em inglês: Only one compilation unit can have top-level statements.
Como consertar: deixe as instruções soltas só no Program.cs. Os outros arquivos ficam só com classes.
error CS0260: Modificador parcial ausente na declaração do tipo "Program"
O que significa: a Ana colou um exemplo antigo, com class Program e Main, embaixo de uma instrução solta. Agora existem duas classes Program: a que o compilador gera para a instrução solta e a do exemplo.
Console.WriteLine("Olá do topo!");
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Olá do Main!");
}
}
$ dotnet run misto.cs /home/ana/cs/misto.cs(3,7): error CS0260: Modificador parcial ausente na declaração do tipo "Program"; existe outra declaração parcial deste tipo Ocorreu uma falha no build. Corrija os erros de build e execute novamente.
A mensagem fala de partial ("parcial"), um recurso que permite dividir uma classe em pedaços (aula Classes Parciais), mas o problema de verdade é a mistura das duas formas. Em inglês: Missing partial modifier on declaration of type 'Program'; another partial declaration of this type exists.
Como consertar: escolha uma forma. Ou apague a instrução solta e fique com o Main, ou tire as linhas de dentro do Main e apague a classe.
warning CS7022: O ponto de entrada do programa é o código global. Ignorando o ponto de entrada 'Inicio.Main(string[])'.
O que significa: a mesma mistura, mas com a classe de outro nome (class Inicio). Não há conflito de nomes, então o programa compila, e o compilador avisa que escolheu as instruções soltas ("o código global") como entrada. O Main da classe Inicio nunca roda:
$ dotnet run misto2.cs /home/ana/cs/misto2.cs(5,17): warning CS7022: O ponto de entrada do programa é o código global. Ignorando o ponto de entrada 'Inicio.Main(string[])'. Olá do topo! « o "Olá do Main!" nunca aparece
É um aviso, e não um erro: o programa roda, mas não do jeito que a Ana esperava. Em inglês: The entry point of the program is global code; ignoring 'Inicio.Main(string[])' entry point.
Como consertar: o mesmo do CS0260: uma forma só.
Referência rápida
| Escrita | O que é |
|---|---|
Console.WriteLine("…"); solto no arquivo | Instrução de nível superior C# 9: o compilador gera Program e <Main>$ |
static void Main(string[] args) { … } | Ponto de entrada clássico, dentro de uma classe |
static int Main() | Ponto de entrada que devolve o código de saída |
args | Os argumentos da linha de comando, nas duas formas |
return 3; (forma curta) | Termina o programa com o código de saída 3 |
namespace Nome; C# 10 | Namespace de arquivo: vale para o arquivo todo, sem chaves |
dotnet new console --use-program-main | Projeto novo na forma clássica |
CS5001 | Falta um static Main (ou instruções soltas) |
CS8803 / CS8802 | Instrução solta depois de classe / em dois arquivos |
CS0260 / CS7022 | As duas formas misturadas |
Exercícios
1. O nome da entrada
Complete a linha do ponto de entrada clássico:
2. A palavra obrigatória
Que palavra falta para este Main ser aceito como ponto de entrada?
3. O código de saída
Complete o tipo para que o Main possa devolver um código de saída com return:
4. Um programa na forma curta tem só Console.WriteLine(args.Length);. De onde vem o args?
5. Num erro, a pilha mostra at Program.<Main>$(String[] args). O que isso indica?
6. Qual é o problema deste arquivo? class Ajuda { } na linha 1 e Console.WriteLine("Oi"); na linha 3.
7. Para um programa novo no .NET 10, o que o guia recomenda?
Missão final: o exemplo do livro
Missão: a Ana copiou este exemplo de um livro antigo e ele não compila. Rode e leia o erro: falta o ponto de entrada. Há dois enganos na linha do método. Conserte a linha (mantendo a forma clássica) para o programa mostrar:
Notas da Ana Matemática: 9 História: 8
using System;
namespace Boletim
{
class Program
{
void main(string[] args)
{
Console.WriteLine("Notas da Ana");
Console.WriteLine("Matemática: 9");
Console.WriteLine("História: 8");
}
}
}
using System;
namespace Boletim
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Notas da Ana");
Console.WriteLine("Matemática: 9");
Console.WriteLine("História: 8");
}
}
}
Depois de consertar, experimente o outro caminho: apague tudo menos as três linhas de Console.WriteLine e rode de novo. A saída é a mesma.
Resumo
- Todo programa tem um ponto de entrada, onde ele começa a rodar. Em C#, é o método
Main. - Forma clássica:
class Programcomstatic void Main(string[] args).Maincom M maiúsculo estaticobrigatório;int Maindevolve o código de saída e oargsé opcional. - Forma curta C# 9: as instruções direto no arquivo. O compilador gera a classe
Programe o método<Main>$, comargs; umreturn 3;vira o código de saída. - As duas formas são o mesmo programa e dão a mesma saída.
dotnet new consolegera a curta;--use-program-main, a clássica (comnamespace Nome;de arquivo). - Regras da forma curta: só um arquivo do projeto (
CS8802) e antes das classes (CS8803). Misturar as duas formas dáCS0260ouCS7022; faltar a entrada dáCS5001. - Use a forma curta em programas novos, e saiba ler a clássica, que está em todo código anterior a 2021.