file based apps

C# 14 for Scripters: No Project Files Required

1. Introduction

Remember when writing a simple “Hello World” in C# required creating a folder, a Solution file, a Project file, and a Program.cs? It felt like building a skyscraper just to house a dog.

In .NET 10, that ceremony is gone.

You can now write a single file—FileName.cs—and run it immediately. It’s as easy as Python or JavaScript, but with the raw power and safety of C#.

2. Decomposition Summary

We will break this down into three simple parts:

  1. The Runner: How dotnet runs your text file.
  2. The Dependencies: Adding libraries (NuGet) without a project file.
  3. The Code: Using the new C# 14 syntax to write less code.

3. What is a “File-Based App”?

In the old days, a C# application was a folder structure. Now, a C# application can be just one text file.

You don’t need to run dotnet build. You just tell the computer to “Run this file,” and .NET handles the rest. It is perfect for small tools, automation scripts, or learning the language.

4. Architecture: The “Invisible” Project

When you run a single file, .NET does a magic trick. It creates a temporary project in the background so you don’t have to.

Visualizing the Flow:

  1. You write: FileName.cs
  2. You run: dotnet run FileName.cs
  3. The CLI:
    • Reads your file.
    • Downloads any packages you listed.
    • Compiles it in memory.
    • Executes it.

5. C# 14 Code Examples

Let’s look at three examples, starting from very simple to powerful.

Example 1: The Basics (No Boilerplate)

No namespace, no class Program, no void Main. Just logic.

// Save as: hello.cs
// Run as: dotnet run hello.cs

var name = args.Length > 0 ? args[0] : "Friend";

Console.WriteLine($"Hello, {name}!");
Console.WriteLine($"Running on .NET {Environment.Version}");

Why this is great: It reads exactly like a script. What you see is what executes.

Example 2: Adding Superpowers (Inline Packages)
Parsing JSON without a .csproj file.

// Save as: fetch-data.cs
// Run as: dotnet run fetch-data.cs

// New .NET 10 Directive to pull packages
#:package Newtonsoft.Json 13.0.3
#:package ConsoleTables 2.5.0

using Newtonsoft.Json.Linq;
using ConsoleTables;
using System.Net.Http;

var client = new HttpClient();
var json = await client.GetStringAsync("https://jsonplaceholder.typicode.com/users");
var users = JArray.Parse(json);

var table = new ConsoleTable("ID", "Name", "City");

foreach (var user in users.Take(5))
{
    table.AddRow(
        user["id"], 
        user["name"], 
        user["address"]?["city"] ?? "Unknown"
    );
}

table.Write();

6. Convert to solution project

Use below command to convert it to solution

dotnet project convert fileName.cs

What happens automatically?

When you run this command, the .NET CLI performs four specific tasks for you:

  1. Scaffolding: It creates a folder MyApp and a MyApp.csproj file.
  2. Package Migration: It reads lines like #:package Newtonsoft.Json from your script and converts them into <PackageReference Include="Newtonsoft.Json" ... /> XML tags in the project file.
  3. Cleanup: It copies your code into Program.cs but strips out the #! (shebang) and #:package lines, as these are not valid in a compiled project.
  4. Namespace Injection: It wraps your code in a namespace MyApp; to follow best practices.

7. Pros & Cons

FeaturePros (The Good)Cons (The Bad)
WorkflowInstant feedback loop; feels like Python/Node.No Solution view; IntelliSense relies on editor support (VS Code is great, full VS is okay).
DependenciesExplicit #:package is clear and portable.Cannot use complex MSBuild logic or custom targets.
PerformanceFull CLR speed; Access to Span<T> and SIMD.Startup time is slower than a pre-compiled binary.
PortabilitySingle file is easy to Gist, email, or git commit.Not suitable for large architectures (dependency injection gets messy).

8. Real-World Applications

When should you actually use this?

  1. CI/CD Pipelines: Replace YAML or Bash with C#. Logic like “If build fails, parse logs, find error, and post to Slack” is trivial in C# but painful in Bash.
  2. Database Migrations: Write ephemeral scripts to massage data using EF Core (via #:package) without creating a permanent project in your solution.
  3. Prototyping: Quickly test a library (e.g., Polly or Serilog) behaviour in isolation before adding it to your main architecture.
  4. Data Cleanup: A quick script to read a CSV file and fix formatting errors.
  5. Daily Tasks: A script to back up your work folder to a zip file every evening.
  6. API Testing: A simple script to call a web API and see the result without opening Postman.

9. Best Practices vs. Anti-Patterns

Best Practices

  • Keep it Single-File: If you need multiple classes, define them at the bottom of the file. If you need multiple files, upgrade to a project.
  • Use #:package Version Pinning: Always specify versions (e.g., #:package Serilog 3.1.1) to ensure the script runs the same way next year.
  • Leverage Top-Level Statements: Don’t write class Program { void Main... }. It defeats the purpose of the concise format.

Anti-Patterns

  • The “God Script”: If your script exceeds 500 lines or requires dependency injection containers, run dotnet project convert and make it a real app.
  • Production APIs: Do not use file-based execution for hosting ASP.NET Core web services in production. The startup delay and lack of build artifacts make deployment brittle.

10. Conclusion

.NET 10 and C# 14 have successfully democratised C# for the scripting world. By removing the project file ceremony and adding features like #:package, Microsoft has given us the best of both worlds: the safety and speed of C#, with the agility of Python.

For your next automation task, put down the PowerShell and pick up a .cs file.

Leave a Reply