Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

19 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 3 de junio de 2025
Desarrolladora ejecutando un archivo .cs con el comando dotnet run

Desde hace ya bastante tiempo, el equipo de .NET está introduciendo mejoras en el SDK para simplificar algunos escenarios y facilitar el acceso a la tecnología de desarrolladores que, o bien están empezando, o bien proceden de otras plataformas.

Una de estas mejoras fueron los top level statements, que permiten escribir los entry points de nuestras aplicaciones C# sin necesidad de definir una clase o un método Main. También las directivas using globales e implícitas ayudaban a reducir el boilerplate necesario para escribir una aplicación C#.

Pero esta vez han ido más lejos 🙂

Con .NET 10, se está trabajando en eliminar toda la ceremonia necesaria para crear y ejecutar una aplicación .NET simple. De la misma forma que se puede hacer en otras plataformas y lenguajes como Node.js o Python, ahora bastará con crear un archivo con extensión .cs y ejecutar el comando dotnet run para compilar y ejecutar el archivo sobre la marcha. Es decir, pasamos de aplicaciones basadas en proyecto a aplicaciones basadas en archivos.

Pero, además, esto abre interesantes posibilidades para la creación de prototipos, pruebas rápidas o incluso para la creación de scripts que aprovechen el poder de .NET y C# sin necesidad de crear un proyecto completo.

Lo vemos en profundidad a continuación.

dotnet run <Filename.cs>

Para probar rápidamente esta nueva funcionalidad, simplemente debemos tener instalado el SDK de .NET 10 (a partir de la preview 4) y crear un archivo con extensión .cs con el código de nuestra aplicación. No hace falta crear proyectos, soluciones, ni carpetas: podemos hacerlo sobre la marcha, en cualquier momento.

Una vez creado, ejecutamos el comando dotnet run seguido del nombre del archivo, y mágicamente éste será compilado y ejecutado. A continuación podéis ver una secuencia en la línea de comandos (CMD) que lo muestra:

D:\test>echo Console.WriteLine("Hello, world!"); > test.cs

D:\test>type test.cs
Console.WriteLine("Hello, world!");

D:\test>dotnet run test.cs
Hello, world!

D:\test>dir /b
test.cs

PS D:\test>_

Ojo: aunque a día de hoy, todavía con la preview 4 de .NET 10, el comando es dotnet run <File.cs>, se está trabajando ya para que en la versión final sea posible prescindir del prefijo run y simplemente ejecutar dotnet <File.cs> 🙂

Fijaos que el archivo se ha compilado y ejecutado, pero no ha aparecido en consola ningún tipo de mensaje o progreso de estas operaciones. Obviamente, si la compilación fallara se mostrarían los errores correspondientes, pero, si todo va bien, simplemente se ejecutará el código y se mostrará su salida.

En la carpeta no se habrá creado ningún archivo adicional, como las clásicas carpetas bin u obj. El compilador de .NET los ha creado en una carpeta temporal, en mi caso ubicada en C:\Users\jmaguilar\AppData\Local\Temp\dotnet\runfile\<FileName+Hash>.

Tampoco vemos ni rastro del archivo de proyecto (.csproj). En realidad, se ha creado en memoria justo en el momento de compilar el archivo, pero no ha sido necesario guardarlo en ninguna parte. Este archivo de proyecto implícito es, por defecto, el mismo que se usaría cuando creamos una aplicación de consola vacía.

Un aspecto que han tenido en cuenta los desarrolladores del SDK de .NET es la compatibilidad hacia atrás de esta característica. Por ello, si intentamos ejecutar un archivo .cs en una carpeta que contiene un archivo de proyecto (.csproj), el comando dotnet run seguirá funcionando como hasta ahora, compilando y ejecutando el proyecto completo. En cambio, si no hay ningún archivo de proyecto y se especifica un archivo C# existente, se lanzará éste directamente.

Uso de directivas

Los archivos .cs que se ejecutan con el comando dotnet pueden contener cualquier tipo de código C#. No estamos usando un compilador diferente, ni un intérprete, ni una biblioteca de clases distinta; son las mismas herramientas que se usan para compilar y ejecutar aplicaciones .NET, pero usadas de una forma diferente.

Eso sí, dado que el archivo de proyecto implícito es el de las aplicaciones de consola, por defecto estaremos utilizando el SDK Microsoft.NET.Sdk. Sin embargo, *podemos usar la directiva #:sdk para especificar otro SDK diferente. Por ejemplo, si introducimos el siguiente código en el archivo Web.cs y ejecutándolo con dotnet run Web.cs, tendremos funcionando toda una aplicación web ASP.NET Core con un endpoint:

#:sdk Microsoft.Net.Sdk.Web

var app = WebApplication.CreateBuilder(args).Build();
app.MapGet("/", () => "Hello World!");
app.Run();
D:\test> dotnet run Web.cs
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\test
_

Como seguro podéis intuir, las directivas deben ir al principio del archivo y comenzar por el prefijo #:.

También tenemos la posibilidad de utilizar paquetes NuGet, esta vez usando la directiva #:package. Por ejemplo, la siguiente aplicación genera por consola un código QR a partir de la URL que le pasemos como argumento, usando el paquete QRCoder:

#:package QRCoder

using QRCoder;

var qrGenerator = new QRCodeGenerator();
var qrData = qrGenerator.CreateQrCode(args[0], QRCodeGenerator.ECCLevel.Q);
var matrix = qrData.ModuleMatrix;

foreach (var row in matrix)
{
    foreach (var module in row)
    {
        Console.Write((bool)module ? "██" : "  ");
    }
    Console.WriteLine();
}
D:\test>dotnet run test.cs https://www.variablenotfound.com

        ██████████████    ██  ██    ██████████      ██████████████
        ██          ██  ██████████████    ██  ████  ██          ██
        ██  ██████  ██    ████  ████            ██  ██  ██████  ██
        ██  ██████  ██  ██          ████████████    ██  ██████  ██
        ██  ██████  ██  ██████        ██  ██████    ██  ██████  ██
        ██          ██    ██    ████      ██    ██  ██          ██
        ██████████████  ██  ██  ██  ██  ██  ██  ██  ██████████████
                        ██  ██████          ██
          ██  ████████  ██████            ██████  ████  ████  ██
        ████  ██  ██    ██  ██████  ██    ██████  ██  ████  ████
          ██████    ██    ████      ████          ████
          ██    ████  ██████  ████  ████  ████  ██        ██    ██
        ████  ████  ██        ██  ████████    ██  ████    ██  ██
              ████        ██      ████  ████        ██  ██  ██  ██
              ██    ████    ██      ██    ██  ████  ████  ██    ██
        ████████████  ██      ████    ████  ████    ██  ██████████
        ██  ████  ████      ██  ██    ██              ██  ██  ██
        ██████  ██    ██    ██  ████    ██████████      ██████
        ████████  ████████  ██  ██    ██      ██      ██  ████  ██
        ██████████    ██████  ████████  ██  ██    ██████    ██
        ████        ██  ██    ██  ██    ████    ██████████████████
                        ██████        ██    ██████      ██████
        ██████████████    ████  ████  ██    ██████  ██  ██  ██
        ██          ██  ██  ████████████████  ████      ████  ████
        ██  ██████  ██  ██    ██  ████████    ████████████    ██
        ██  ██████  ██  ██  ██    ████████  ██        ██    ██  ██
        ██  ██████  ██      ████████████    ██  ████  ████    ████
        ██          ██  ██████  ██    ██          ████  ██████  ██
        ██████████████      ██████      ██    ████████████

D:\test>_

Otra directiva interesante es #:property, que permite definir propiedades de construcción del proyecto, de la misma forma que si las estableciéramos en el archivo .csproj en los proyectos convencionales. Por ejemplo, la siguiente propiedad hace que el proyecto sea compilado para .NET 9.0:

#:property TargetFramework net9.0
Console.WriteLine(RuntimeInformation.FrameworkDescription);

Por último, en Linux podemos usar la sintaxis shebang en el archivo C# para indicar al sistema cómo debe ejecutarlo. Simplemente indicamos en la primera línea la referencia a dotnet run, y, si el archivo dispone de los permisos apropiados, podremos lanzarlo directamente desde la línea de comandos:

#!/usr/bin/dotnet run
Console.WriteLine("Hello from a C# script!");
chmod +x app.cs
./app.cs
Hello from a C# script!

¿Y si un proyecto de un único archivo se queda pequeño?

Pues sí, a veces ocurre. Algo que empieza como un simple script o una prueba rápida de usar y tirar acaba creciendo más de la cuenta. Pero para estos casos, también se ha previsto una solución: en cualquier momento podemos convertir el archivo .cs en un proyecto de .NET completo.

Para ello, simplemente ejecutamos el comando dotnet project convert seguido del nombre del archivo. Este comando creará una carpeta para el nuevo proyecto, y en su interior depositará un archivo .csproj y el archivo .cs original, aunque eliminándole las directivas (que habrán pasado al archivo de proyecto generado).

D:\test>dotnet project convert Main.cs

D:\test>cd Main

D:\test\Main>dir /b
Main.cs
Main.csproj

Todavía en desarrollo, ¿qué podemos esperar?

Esta característica aún está en desarrollo, pero en el repositorio pueden verse algunas líneas en las que se está trabajando, entre otras:

  • Permitir que el comando dotnet run pueda aceptar múltiples archivos .cs, de forma que podamos ejecutar aplicaciones más complejas sin necesidad de crear un proyecto.
  • Mejorar el tooling, para que Visual Studio y Visual Studio Code puedan reconocer estos archivos y ofrecer una experiencia de desarrollo más completa, como la navegación por el código, la depuración o el autocompletado.
  • Mejorar el rendimiento de la compilación y ejecución de estos archivos, para que la experiencia sea lo más fluida posible.
  • Y, como comentaba algo más arriba, simplificar la sintaxis para que no sea necesario usar el prefijo run al ejecutar un archivo .cs, de forma que podamos escribir simplemente dotnet <File.cs>.

Si queréis ver más, podéis seguir los issues en el repositorio de GitHub del SDK de .NET

¡Espero que os haya resultado interesante!

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!