Table of Contents
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:
- The Runner: How dotnet runs your text file.
- The Dependencies: Adding libraries (NuGet) without a project file.
- 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:
- You write:
FileName.cs - You run:
dotnet runFileName.cs - 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 convertfileName.cs
What happens automatically?
When you run this command, the .NET CLI performs four specific tasks for you:
- Scaffolding: It creates a folder
MyAppand aMyApp.csprojfile. - Package Migration: It reads lines like
#:package Newtonsoft.Jsonfrom your script and converts them into<PackageReference Include="Newtonsoft.Json" ... />XML tags in the project file. - Cleanup: It copies your code into
Program.csbut strips out the#!(shebang) and#:packagelines, as these are not valid in a compiled project. - Namespace Injection: It wraps your code in a
namespace MyApp;to follow best practices.
7. Pros & Cons
| Feature | Pros (The Good) | Cons (The Bad) |
| Workflow | Instant feedback loop; feels like Python/Node. | No Solution view; IntelliSense relies on editor support (VS Code is great, full VS is okay). |
| Dependencies | Explicit #:package is clear and portable. | Cannot use complex MSBuild logic or custom targets. |
| Performance | Full CLR speed; Access to Span<T> and SIMD. | Startup time is slower than a pre-compiled binary. |
| Portability | Single 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?
- 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.
- Database Migrations: Write ephemeral scripts to massage data using EF Core (via
#:package) without creating a permanent project in your solution. - Prototyping: Quickly test a library (e.g.,
PollyorSerilog) behaviour in isolation before adding it to your main architecture. - Data Cleanup: A quick script to read a CSV file and fix formatting errors.
- Daily Tasks: A script to back up your work folder to a zip file every evening.
- 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
#:packageVersion 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 convertand 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.