File-Based Apps: C# sin proyecto ejecuta tan rápido como Python

File-Based Apps: C# sin proyecto ejecuta tan rápido como Python

  1. .NET
  2. 1 day ago
  3. 3 min read

De top-level statements a ejecución directa

Antes (hasta .NET 9):

Podías usar top-level statements, pero seguías necesitando un .csproj. La fricción estaba en la estructura, no en la sintaxis.

Ahora (.NET 10):

procesar-ordenes.cs
#:package CsvHelper@33.0.1
#:package RestSharp@112.1.0
using CsvHelper;
using RestSharp;
using System.Globalization;
var ordenes = new CsvReader(
new StreamReader("ordenes.csv"),
CultureInfo.InvariantCulture
).GetRecords<Orden>().ToList();
var client = new RestClient("https://api.empresa.com");
foreach (var orden in ordenes)
{
await client.PostJsonAsync("/ordenes", orden);
}
Console.WriteLine($"{ordenes.Count} órdenes procesadas");
record Orden(string Id, decimal Total, DateTime Fecha);

Ejecutas:

Terminal window
dotnet run procesar-ordenes.cs

Primera ejecución: 2.8s (compila + cachea). Segunda: 85ms. Sin proyecto. Sin boilerplate.

Esto sí compite con Python en velocidad de desarrollo.


Por qué el caché cambia todo

La mayoría piensa que File-Based Apps es “sintaxis conveniente”. El diferenciador es el caché inteligente del CLI que persiste compilación entre ejecuciones.

Qué cachea:

  • Compilación del código
  • Resolución de dependencias
  • Proyecto virtual generado

Cuándo se invalida:

  • Cambias el código
  • Modificas versiones de paquetes
  • Actualizas el SDK

Escenario real - ETL de datos:

etl-ventas.cs
#:package Dapper@2.1.35
#:package Npgsql@8.0.5
using Npgsql;
using Dapper;
var connOrigen = new NpgsqlConnection("Server=legacy;Database=ventas_old");
var connDestino = new NpgsqlConnection("Server=prod;Database=analytics");
var ventas = await connOrigen.QueryAsync<Venta>(@"
SELECT id, monto, fecha, cliente_id
FROM ventas
WHERE fecha >= @fecha
", new { fecha = DateTime.Today.AddMonths(-1) });
await connDestino.ExecuteAsync(@"
INSERT INTO ventas_procesadas (venta_id, total, periodo, cliente)
VALUES (@Id, @Monto, @Fecha, @ClienteId)
", ventas);
Console.WriteLine($"Migradas {ventas.Count()} ventas");
record Venta(int Id, decimal Monto, DateTime Fecha, int ClienteId);

Desarrollo iterativo:

  • Primera run: 3.2s
  • Ajustas la query, reexecutas: 90ms
  • Cambias el mapping: 90ms
  • Pruebas con otra fecha: 90ms

Sin proyecto. Sin rebuild manual. Caché hace el trabajo.


Scripts de automatización y procesamiento one-off

Limpiar logs antiguos:

limpiar-logs.cs
var logsDir = new DirectoryInfo("/var/logs/app");
var archivosViejos = logsDir.GetFiles()
.Where(f => f.CreationTime < DateTime.Now.AddDays(-30));
foreach (var archivo in archivosViejos)
{
archivo.Delete();
Console.WriteLine($"Eliminado: {archivo.Name}");
}

Ejecutas localmente, funciona, commiteas el .cs al repo. Otros devs lo ejecutan directo.

Prototipos de APIs:

test-api.cs
#:package RestSharp@112.1.0
var client = new RestClient("https://api.staging.com");
var response = await client.GetAsync<Producto>("/productos/123");
Console.WriteLine(response?.Nombre ?? "No encontrado");
record Producto(string Nombre, decimal Precio);

Migración manual de datos, limpieza de BD, análisis de logs. Lo ejecutas una vez o pocas veces. El caché elimina fricción en iteraciones.


Cuándo convertir a proyecto tradicional

Señales claras:

  • Más de 200 líneas: La estructura de proyecto aporta más que resta
  • Múltiples archivos: File-Based Apps es mono-archivo (hasta .NET 11)
  • Ejecución programada en producción: CI/CD y contenedores no mantienen caché
  • Performance crítica: Necesitas AOT, trimming, o optimizaciones de publicación

Conversión automática:

Terminal window
dotnet project convert mi-script.cs

Genera .csproj, preserva #:package como <PackageReference>. Ahora tienes estructura completa sin reescribir código.


Type-safety sin fricción de setup

File-Based Apps no reemplazan proyectos. Cierran la brecha con lenguajes de scripting sin sacrificar el sistema de tipos de C#.

Antes: “Para este script rápido uso Python aunque mi stack sea .NET”. Ahora: “Uso C# con type-safety, async/await, LINQ, y todo el ecosistema sin fricción de setup”.

El caché hace que iterar sea instantáneo. Las directivas #:package eliminan gestión manual de dependencias. Un archivo .cs es portable y ejecutable.

Usa File-Based Apps para exploración y automatización. Migra a proyecto cuando la complejidad o el deployment lo justifiquen. La barrera de entrada bajó. La decisión de cuándo escalar sigue siendo tuya.

csharp dotnet productivity