Introdução

Toda vez que preciso de uma pequena automação no dia a dia — verificar uma configuração, importar um CSV, chamar uma API interna — a pergunta inevitável aparece: uso PowerShell, Python, ou crio um projeto .NET completo? Durante anos a resposta padrão para times .NET era criar um projeto console, mesmo para tarefas de cinco minutos. Isso mudou. Hoje é possível executar C# como script no .NET sem criar solução, sem .csproj, sem boilerplate.

Neste artigo vou mostrar as três abordagens disponíveis, quando cada uma faz sentido e como incorporar isso no dia a dia de um time .NET. Os exemplos completos estão no repositório blog-zocateli-sample — no artigo vou mostrar apenas os trechos essenciais para explicar cada conceito.

As três abordagens são:

  • Top-level statements — eliminam o boilerplate de class Program e static void Main(), mas ainda precisam de um .csproj
  • dotnet-script — executa arquivos .csx sem projeto, com suporte a NuGet inline
  • dotnet run arquivo.cs — novidade do .NET 10 que executa um único arquivo .cs sem nenhuma configuração

Para quem já tem familiaridade com .NET e quer aproveitar C# para automações do cotidiano, as três abordagens têm casos de uso complementares — e entender as diferenças é o que vai guiar a escolha certa na hora certa.

Recomendo a leitura do artigo sobre Pipeline de Configuração .NET 8+ para entender como gerenciar segredos e variáveis de ambiente, algo que inevitavelmente aparece em scripts de automação.

Pré-requisitos

Para acompanhar os exemplos práticos, você vai precisar de:

  • .NET SDK 10 instalado — verifique com dotnet --version (deve retornar 10.x.x)
  • dotnet-script instalado como global tool:
1
dotnet tool install -g dotnet-script

Após a instalação, verifique com dotnet script --version.

  • Familiaridade básica com C# e linha de comando

O Que É C# como Script?

Historicamente, executar código C# exigia criar uma solução, um projeto .csproj, escrever um Program.cs com namespace, class Program e static void Main(string[] args) — só depois vinha o código em si. Para uma tarefa de dez linhas, o overhead era desproporcional.

A ideia de usar C# como linguagem de script não é nova: o Roslyn Scripting API existe desde 2015 e é a base do dotnet-script. O que mudou ao longo do tempo foi a integração dessa capacidade diretamente no fluxo de trabalho .NET:

  • .NET 5 (2020): top-level statements eliminam o boilerplate no Program.cs, mas o projeto ainda é necessário
  • dotnet-script (independente da versão do SDK): ferramenta de terceiros que executa .csx sem projeto nenhum
  • .NET 10 (2025): dotnet run arquivo.cs executa um único arquivo .cs diretamente, sem projeto

A diferença conceitual entre “script” e “aplicação” está no ciclo de vida e na intenção: um script é executado uma vez, resolve uma tarefa específica e não precisa de distribuição formal. Uma aplicação tem ciclo de vida gerenciado, deploys, versionamento. Quando o script começa a crescer — múltiplos arquivos, testes, configuração externalizada — o momento de criar um projeto dedicado chegou.

A tabela abaixo resume as três abordagens:

AbordagemExtensãoRequer projetoNuGet inline.NET mínimo
Top-level statements.csSim (mas mínimo)Não.NET 5
dotnet-script.csxNãoSim (#r "nuget:...").NET Core 3.1+
dotnet run arquivo.csNãoNão (ainda).NET 10

Comparação das três abordagens de C# como script: top-level statements, dotnet-script e dotnet run

ℹ️ Informação: As três abordagens são complementares, não concorrentes. A escolha depende do contexto: complexidade do script, necessidade de pacotes externos e versão do SDK disponível no ambiente.

Top-Level Statements: C# Moderno sem Boilerplate

Os top-level statements foram introduzidos no C# 9 (.NET 5) e permitem escrever o código diretamente no arquivo Program.cs, sem declarar namespace, class ou Main. O compilador gera tudo isso automaticamente.

A diferença visual é clara:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Antes — C# 8 e anteriores
using System;

namespace MeuApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Olá, mundo!");
        }
    }
}
1
2
// Depois — C# 9+ com top-level statements
Console.WriteLine("Olá, mundo!");

Ambos compilam para o mesmo resultado. O compilador infere a classe e o método de entrada. Quando preciso de argumentos de linha de comando, o array args está disponível implicitamente:

1
2
3
// args está disponível sem declaração explícita
var ambiente = args.Length > 0 ? args[0] : "desenvolvimento";
Console.WriteLine($"Executando em: {ambiente}");

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog: BlogSamples/Scripting/TopLevelStatements/

⚠️ Atenção: Top-level statements ainda exigem um arquivo .csproj para compilar. Eles removem o boilerplate da classe e do método Main, mas não eliminam o projeto. Se você quer executar código sem projeto nenhum, as próximas duas abordagens são o caminho.

A limitação principal é que apenas um arquivo por projeto pode usar top-level statements — os demais precisam de declarações normais. Na prática isso é suficiente para a maioria dos projetos console.

dotnet-script: Executando Arquivos .csx

O dotnet-script é uma ferramenta global que interpreta arquivos .csx (C# Script) usando o Roslyn Scripting API. A diferença fundamental para top-level statements é que não precisa de projeto — o arquivo é a unidade de execução completa.

1
2
3
4
5
# Instalar uma única vez
dotnet tool install -g dotnet-script

# Executar um script
dotnet script meu-script.csx

O recurso mais poderoso é a referência a pacotes NuGet diretamente no arquivo, sem csproj, sem dotnet add package:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// exemplo-com-nuget.csx
#r "nuget: Humanizer, 2.14.1"

using Humanizer;

var bytes = 1_048_576L;
// Humanizer converte para leitura humana
Console.WriteLine(bytes.Bytes().Humanize()); // "1 MB"

var data = DateTime.Now.AddDays(-3);
Console.WriteLine(data.Humanize()); // "3 days ago"

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog: BlogSamples/Scripting/DotnetScript/

Os casos de uso onde o dotnet-script brilha:

  • Migrations avulsas: rodar uma migração pontual em ambiente específico sem o contexto do projeto principal
  • Seed de dados: popular banco de dados de desenvolvimento com dados realistas
  • Utilitários de CI/CD: verificar versões, validar configurações, gerar artefatos
  • Consultas e relatórios: combinar Entity Framework (via NuGet) com lógica C# sem criar projeto

💡 Dica: No Linux e macOS, adicione #!/usr/bin/env dotnet-script na primeira linha do arquivo .csx para torná-lo executável diretamente no shell, sem precisar prefixar com dotnet script.

Executando um Arquivo .cs Diretamente com dotnet run

O .NET 10 trouxe uma funcionalidade aguardada: executar um único arquivo .cs sem projeto, sem solução, sem configuração nenhuma:

1
dotnet run meu-script.cs

Isso é diferente do dotnet run tradicional que precisa ser executado dentro de um diretório de projeto. Aqui, o arquivo .cs é a unidade completa de execução.

Um exemplo de uso real — verificar a versão de um serviço em múltiplos ambientes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// verificar-versao.cs
using System.Net.Http;
using System.Text.Json;

var ambientes = new[] { "dev", "staging", "prod" };
var http = new HttpClient();

foreach (var env in ambientes)
{
    var url = $"https://api.{env}.empresa.com/health";
    var resposta = await http.GetStringAsync(url);
    var json = JsonDocument.Parse(resposta);
    var versao = json.RootElement.GetProperty("version").GetString();
    Console.WriteLine($"{env,-10} → {versao}");
}
1
dotnet run verificar-versao.cs

📂 Código Fonte: O exemplo completo está disponível no repositório de exemplos do blog: BlogSamples/Scripting/DotnetRunSingleFile/

ℹ️ Informação: Na versão inicial do .NET 10, dotnet run arquivo.cs não suporta múltiplos arquivos nem referências NuGet inline. Para scripts que precisam de pacotes externos, o dotnet-script com .csx ainda é a melhor opção.

A maior vantagem dessa abordagem é a fricção zero: não há instalação de ferramenta adicional, funciona em qualquer máquina com .NET 10 SDK e não exige nenhum arquivo auxiliar.

Casos de Uso Reais: Quando C# como Script Faz Sentido

Após usar as três abordagens no dia a dia, alguns cenários onde a escolha por C# como script se pagou:

Automações de CI/CD: scripts de pipeline que precisam de lógica de negócio — validar configurações de appsettings.json, verificar que variáveis de ambiente obrigatórias estão presentes, gerar artefatos de release com nomes padronizados. Tudo isso em C# tipado, com as mesmas bibliotecas que o projeto principal usa.

Data seeding de desenvolvimento: popular um banco de dados local com dados realistas usando o mesmo DbContext e as mesmas entidades do projeto. Com dotnet-script e #r "nuget:Microsoft.EntityFrameworkCore.SqlServer,...", o script acessa o banco sem criar um projeto dedicado.

Utilitários de equipe: ferramentas internas que o time .NET precisa manter ao longo do tempo. Em C# com tipagem estática, o próximo desenvolvedor que abrir o script vai entender o código sem aprender uma nova linguagem.

Prototipação: testar o comportamento de uma API ou biblioteca antes de integrá-la ao projeto principal. Um arquivo .csx com #r "nuget:..." é mais rápido do que criar um projeto de testes.

⚠️ Atenção: Quando o script começa a crescer — múltiplas funções, múltiplos arquivos, necessidade de testes unitários, configuração externalizada — o momento de criar um projeto console dedicado chegou. Scripts são para tarefas pontuais, não para sistemas. O artigo sobre .NET Worker Background Service mostra o caminho quando a automação precisa virar serviço de longa duração.

Comparativo: C# Script vs PowerShell vs Python para Automações

Para times .NET, a comparação com as alternativas mais comuns é relevante. A tabela abaixo é objetiva — cada ferramenta tem vantagens reais:

CritérioC# ScriptPowerShellPython
Curva de aprendizado (time .NET)BaixaMédiaAlta
Acesso a bibliotecas .NET / NuGet✅ Nativo❌ Limitado❌ Não
Tipagem estática✅ Sim❌ Não❌ Não
Portabilidade (Linux/macOS/Windows)✅ Sim✅ Sim (PS Core)✅ Sim
Suporte a IDE (IntelliSense, debug)✅ VS Code + OmniSharpParcial✅ Sim
Performance de startup (JIT warm-up)⚠️ LentoMédioMédio
Administração do sistema operacional⚠️ Verboso✅ NativoBom
Ecossistema de ML / data science❌ Não❌ Não✅ Sim

Quando C# script perde: tarefas de shell puro (mover arquivos, gerenciar processos, pipeline de comandos), administração de sistema operacional, scripts de menos de dez linhas onde PowerShell é mais conciso.

Quando C# script ganha: lógica de negócio que usa os mesmos tipos e bibliotecas do projeto principal, consumo de APIs .NET, operações de banco via Entity Framework, qualquer coisa onde tipagem estática reduz erros.

Exemplo Prático

O cenário: um script que lê um arquivo CSV com produtos e envia cada registro para uma API REST interna — uma necessidade recorrente em projetos de migração de dados.

Com dotnet-script, o arquivo .csx referencia o pacote CSV diretamente:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// importar-csv-api.csx (trecho principal)
#r "nuget: CsvHelper, 33.0.1"

using CsvHelper;
using System.Globalization;
using System.Net.Http.Json;

var apiUrl = Environment.GetEnvironmentVariable("API_URL")
    ?? throw new InvalidOperationException("Variável API_URL não configurada");

using var http = new HttpClient();
using var reader = new StreamReader("produtos.csv");
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);

var produtos = csv.GetRecords<Produto>().ToList();
Console.WriteLine($"Lidos {produtos.Count} produtos. Enviando...");

foreach (var produto in produtos)
{
    var resposta = await http.PostAsJsonAsync($"{apiUrl}/produtos", produto);
    resposta.EnsureSuccessStatusCode();
    Console.WriteLine($"  ✓ {produto.Nome}");
}

📂 Código Fonte: O exemplo completo, com tratamento de erros, cancelamento via CancellationToken, retry e log estruturado está disponível no repositório de exemplos do blog: BlogSamples/Scripting/ExemploPratico/

O script completo no repositório inclui o record Produto, tratamento de HttpRequestException com retry manual, log de progresso e suporte a --dry-run via argumento.

Dicas e Boas Práticas

  • Prefira .csx com dotnet-script quando precisar de NuGet. Top-level statements num projeto .csproj mínimo ainda exigem dotnet add package e alteram o arquivo de projeto. Com .csx, a referência fica dentro do próprio script e qualquer pessoa pode executar com dotnet script arquivo.csx sem setup adicional.

  • Nunca hardcode segredos em scripts. Use variáveis de ambiente (Environment.GetEnvironmentVariable) ou o dotnet user-secrets para desenvolvimento local. Scripts de automação frequentemente circulam em repositórios e logs de CI — uma connection string ou token expostos são um risco real. Veja o artigo sobre Logging Estruturado para entender como evitar dados sensíveis em logs também.

  • Para scripts usados em CI/CD, fixe a versão da ferramenta. Crie um .config/dotnet-tools.json no repositório com dotnet tool install --local dotnet-script. Isso garante que o pipeline sempre usa a mesma versão, sem depender do que está instalado globalmente no agente de build.

  • Adicione #!/usr/bin/env dotnet-script para Linux/macOS. Com essa linha na primeira linha do arquivo .csx e permissão de execução (chmod +x script.csx), o script pode ser chamado diretamente como ./script.csx no terminal — sem prefixo. Em times com desenvolvedores em diferentes sistemas operacionais, isso reduz fricção.

  • Defina um limite de crescimento. Scripts acima de 150–200 linhas geralmente indicam que a tarefa cresceu além do escopo de um script. Nesse ponto, criar um projeto console com estrutura adequada — testes, configuração externalizada, injeção de dependência — é mais sustentável do que expandir o script indefinidamente.

Resumo Objetivo

  • Top-level statements — eliminam o boilerplate de class Program e static void Main() desde o C# 9 (.NET 5), mas ainda exigem um arquivo .csproj para compilação; não são execução sem projeto.
  • dotnet-script — ferramenta global open-source que executa arquivos .csx sem projeto, com suporte a referências NuGet inline via #r "nuget:Pacote,Versão", baseada no Roslyn Scripting API.
  • dotnet run arquivo.cs — recurso nativo do .NET 10 que executa um único arquivo .cs sem projeto, sem solução e sem configuração adicional; não suporta NuGet inline na versão inicial.
  • C# como script no CI/CD — permite que times .NET escrevam automações de pipeline reutilizando bibliotecas e tipagem do ecossistema .NET, sem aprender PowerShell ou Python.
  • Limitação do dotnet run arquivo único (.NET 10) — não suporta múltiplos arquivos nem NuGet inline; para essas necessidades, dotnet-script com .csx é a alternativa adequada.
  • Critério de escolha — sem NuGet inline e sem projeto: dotnet run arquivo.cs; com NuGet inline e sem projeto: dotnet-script .csx; com distribuição formal e múltiplos arquivos: projeto console com top-level statements.

Leia Também

Referências