Proyecto .NET moderno con top-level statements

Básico
C#
C#
Actualizado: 21/04/2026

El flujo habitual al crear una aplicación de consola con el CLI de .NET produce un proyecto mucho más compacto que el clásico esqueleto con class Program y Main. Las plantillas actuales apuestan por un archivo Program.cs minimalista donde la lógica principal se escribe directamente, sin ceremonia intermedia. Este enfoque es el que se encuentra en cualquier tutorial actualizado y conviene conocerlo desde el primer día.

Top-level statements en Program.cs

Las top-level statements, o sentencias de nivel superior, permiten escribir el código de entrada de la aplicación sin declarar explícitamente una clase ni un método Main. El compilador se encarga de generar toda esa estructura por debajo. El resultado es un archivo Program.cs legible de un vistazo.

Al ejecutar el comando de creación en una terminal, se genera un proyecto con un archivo muy breve.

dotnet new console -n MiPrograma
cd MiPrograma
dotnet run

El contenido del archivo Program.cs generado tiene este aspecto.

Console.WriteLine("Hola, .NET moderno.");

Una sola línea basta para compilar y ejecutar. Se pueden añadir más sentencias, declarar variables locales e incluso definir funciones locales sin tocar la estructura clásica.

var nombre = "Sofía";
var saludo = ConstruirSaludo(nombre);
Console.WriteLine(saludo);

static string ConstruirSaludo(string persona) => $"Hola, {persona}.";

Las funciones locales declaradas con static no capturan estado y pueden convivir con el resto de sentencias. Esto resulta útil para scripts, herramientas internas y casos donde la ceremonia clásica estorba.

Las top-level statements están limitadas a un único archivo por proyecto. Si la aplicación crece, se mueve la lógica a clases y se deja Program.cs como punto de arranque compacto.

Argumentos y retorno desde top-level

Aunque no se escriba Main de forma explícita, el compilador mantiene las capacidades tradicionales. Existe una variable implícita args de tipo string[] con los argumentos de la línea de comandos.

if (args.Length == 0)
{
    Console.Error.WriteLine("Falta un argumento obligatorio.");
    return 1;
}

Console.WriteLine($"Procesando archivo {args[0]}");
return 0;

Devolver un valor entero desde las top-level statements equivale al código de salida del proceso, igual que con Main tradicional. Esto encaja con herramientas de línea de comandos y pipelines de CI donde un código distinto de cero se interpreta como fallo.

flowchart LR
  A[dotnet run] --> B[Compilador genera Main implícito]
  B --> C[Ejecución top-level]
  C --> D{return int?}
  D -->|sí| E[ExitCode entero]
  D -->|no| F[ExitCode 0]

Implicit usings en el archivo .csproj

Los proyectos modernos activan los implicit usings de forma predeterminada. La propiedad se encuentra en el archivo de proyecto y se llama ImplicitUsings. Cuando está habilitada, el SDK inyecta automáticamente los namespaces más habituales del tipo de proyecto.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

Entre los namespaces que se inyectan en una aplicación de consola están System, System.IO, System.Linq, System.Collections.Generic y System.Threading.Tasks. Por eso es posible escribir Console.WriteLine o List<int> sin ningún using visible en el archivo.

El listado exacto depende del SDK y del tipo de proyecto. En una Web API se añaden otros namespaces como Microsoft.AspNetCore.Builder o Microsoft.Extensions.Hosting.

Global usings para compartir imports

Las directivas global using permiten declarar un using una sola vez para todo el proyecto. Se colocan normalmente en un archivo llamado GlobalUsings.cs situado en la raíz del proyecto.

global using System.Text.Json;
global using System.Globalization;

global using static System.Math;

A partir de ese momento, cualquier archivo .cs del proyecto puede usar JsonSerializer, CultureInfo o la función Sqrt sin repetir imports. La variante global using static trae también los miembros estáticos del tipo indicado.

var radio = 3.0;
var area = PI * Pow(radio, 2);
Console.WriteLine($"Área: {area}");

Esta técnica es útil para centralizar los namespaces habituales del dominio de la aplicación, como logging, validación o acceso a datos, evitando repetir las mismas diez líneas al principio de cada archivo.

File-scoped namespaces

Un namespace con ámbito de archivo se declara terminado en punto y coma y abarca el resto del contenido del archivo sin necesidad de llaves adicionales. La indentación baja un nivel entero y la lectura gana en claridad.

namespace Facturacion.Servicios;

public class CalculadoraImpuestos
{
    private const decimal Iva = 0.21m;

    public decimal Aplicar(decimal baseImponible)
    {
        return baseImponible * (1 + Iva);
    }
}

La forma clásica con llaves sigue siendo válida, pero la nueva convención en proyectos nuevos es declararlo siempre con file-scoped namespace. Un único archivo no puede contener dos namespaces distintos bajo este formato, lo cual también es una buena práctica organizativa.

Mantener un namespace por archivo ayuda a que la estructura de carpetas del proyecto refleje la estructura lógica del código.

Estructura completa de un proyecto moderno

Uniendo todas las piezas, un proyecto C# actual tiene esta apariencia general. El archivo de proyecto habilita los features del SDK, un archivo de usings globales concentra los imports comunes y el resto de archivos declaran namespaces con punto y coma.

El archivo MiApi.csproj activa las opciones del SDK.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net9.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>
</Project>

El archivo GlobalUsings.cs concentra los imports comunes del dominio.

global using Facturacion.Servicios;
global using Facturacion.Modelos;

El archivo Program.cs arranca la aplicación con top-level statements.

var calculadora = new CalculadoraImpuestos();
var totales = args.Select(a => decimal.Parse(a)).Select(calculadora.Aplicar);

foreach (var total in totales)
{
    Console.WriteLine($"Total con IVA: {total:C}");
}

El archivo Modelos/Factura.cs declara su namespace con ámbito de archivo.

namespace Facturacion.Modelos;

public class Factura
{
    public string Numero { get; init; } = string.Empty;
    public decimal BaseImponible { get; init; }
}

Con esta combinación, cada archivo se concentra en su responsabilidad, sin repetir using ni llaves innecesarias. El código queda más denso en intención y más ligero en ceremonia.

Reestructurar proyectos antiguos

Un proyecto heredado suele tener Program.cs con la forma clásica, namespaces anidados y una lista larga de usings en cada archivo. Migrar al estilo moderno se hace de forma progresiva sin romper nada.

El primer paso es activar ImplicitUsings y Nullable en el .csproj. El compilador indicará que usings ya no son necesarios y los atenuará en el editor.

El segundo paso es extraer los usings repetidos a un GlobalUsings.cs. Es común encontrar using Microsoft.Extensions.Logging y using System.Text.Json en decenas de archivos. Una sola línea global elimina todas esas repeticiones.

El tercer paso es convertir los namespaces con llaves en namespaces de archivo. La acción está disponible en el IDE como refactor automático y no cambia la compilación, solo la sintaxis.

Realizar la migración módulo a módulo y con commits pequeños facilita la revisión y permite revertir cambios puntuales si algo se comporta de forma inesperada.

Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, C# es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de C#

Explora más contenido relacionado con C# y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Escribir proyectos C# modernos usando top-level statements, global usings, implicit usings y file-scoped namespaces para eliminar ceremonia innecesaria.