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.

Você vai aprender
  • O que é o ponto de entrada de um programa.
  • A forma clássica, com class Program e static 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, CS0260 e o aviso CS7022.

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 Program e o método Main com 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ê escrever Console em vez do nome completo, System.Console. Nos projetos novos, essa linha vem implícita (o ImplicitUsings do .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 Program de 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. O internal ("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 Main antes de existir qualquer objeto, por isso ele precisa ser static (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: main nã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 Mainvocê: 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:

AssinaturaRecebe os argumentos?Devolve o código de saída?
static void Main()NãoNão (sempre 0)
static void Main(string[] args)SimNão (sempre 0)
static int Main()NãoSim, com return
static int Main(string[] args)SimSim, com return
static async Task Main(…) e static async Task<int> Main(…)OpcionalSó 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

EscritaO que é
Console.WriteLine("…"); solto no arquivoInstruçã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
argsOs 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# 10Namespace de arquivo: vale para o arquivo todo, sem chaves
dotnet new console --use-program-mainProjeto novo na forma clássica
CS5001Falta um static Main (ou instruções soltas)
CS8803 / CS8802Instrução solta depois de classe / em dois arquivos
CS0260 / CS7022As duas formas misturadas

Exercícios

1. O nome da entrada

Complete a linha do ponto de entrada clássico:

static void (string[] args)

2. A palavra obrigatória

Que palavra falta para este Main ser aceito como ponto de entrada?

void Main(string[] args)

3. O código de saída

Complete o tipo para que o Main possa devolver um código de saída com return:

static Main()

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 Program com static void Main(string[] args). Main com M maiúsculo e static obrigatório; int Main devolve o código de saída e o args é opcional.
  • Forma curta C# 9: as instruções direto no arquivo. O compilador gera a classe Program e o método <Main>$, com args; um return 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 console gera a curta; --use-program-main, a clássica (com namespace Nome; de arquivo).
  • Regras da forma curta: só um arquivo do projeto (CS8802) e antes das classes (CS8803). Misturar as duas formas dá CS0260 ou CS7022; 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.