Run C# Scripts With dotnet run app.cs: No Project Files Needed
Learn to run C# scripts with dotnet run app.cs in .NET 10 — no project files, no setup. Add NuGet packages, pass arguments, and automate tasks fast.
Think about how you cook a quick snack at home, like a plate of Maggi. You do not book a restaurant, hire a chef, and set up a full kitchen line. You grab one pot, boil some water, and you are done in two minutes. But when you want to cook a big feast for a wedding, then you set up the whole kitchen with many helpers.
Writing small C# programs used to feel like booking the whole wedding kitchen just to make two minutes of Maggi. Before you could run even one line of code, you needed a project file, folders, and setup. That felt like too much for a tiny job.
.NET 10 fixes this. Now you can write a C# script in one file and run it straight away with dotnet run app.cs. No project. No setup. Just your code and one command. This is perfect for small tools, automation, and quick experiments. Let us learn how it works, step by step.
What "running a script" means here
A script is a small program you run to do one job. Maybe it renames a hundred files. Maybe it checks if a website is up. Maybe it reads a CSV and prints a summary. For years, people wrote these little jobs in Python, PowerShell, or Bash, because C# felt too heavy for them.
Now C# can play in that space too. You write your code in a file like cleanup.cs, and you run it:
// cleanup.cs
Console.WriteLine("Cleaning up old files...");
var files = Directory.GetFiles("logs", "*.tmp");
foreach (var file in files)
{
File.Delete(file);
Console.WriteLine($"Deleted {file}");
}
Console.WriteLine($"Done. Removed {files.Length} files.");dotnet run cleanup.csThat is the whole thing. No .csproj file. No class Program. No static void Main. You write the steps you want, top to bottom, and run them.
This works because of top-level statements. That C# feature lets you skip the usual wrapper code. The lines you write are the program. The .NET SDK reads your file, quietly builds a project for you behind the curtain, and runs it.
Why this is exciting
Before .NET 10, the smallest "real" C# program still needed a project. If you came from Python, where you save one file and run it, C# felt slow to start. This new way removes that wall. It is great for:
- Automation — small helper tools that do one job well.
- Learning — a student can try an idea without any setup.
- Prototyping — test a tiny idea in seconds, then throw it away.
Your first script in three steps
Let me walk you through making and running a script. It really is just three small steps.
From idea to running script
Steps
Write code
Use top-level statements
Save .cs file
Name it greet.cs
dotnet run
Run greet.cs
First, write your code in any text editor. Here is a script that greets a person by name:
// greet.cs
var name = args.Length > 0 ? args[0] : "friend";
Console.WriteLine($"Namaste, {name}!");
Console.WriteLine($"The time is {DateTime.Now:hh:mm tt}.");Notice the args word. That is the list of words you type after the file name. You did not have to declare it; in a file-based app, args is just there for you to use.
Second, save the file as greet.cs.
Third, run it:
dotnet run greet.cs -- AaravYou will see Namaste, Aarav! and the current time. Everything after the -- is sent to your program, not to the dotnet tool. So args[0] becomes Aarav. If you skip the name, the script uses friend instead.
What happens behind the scenes
When you type dotnet run greet.cs, a few things happen that you never see. Knowing them helps you trust the tool and understand why the second run is faster.
The SDK reads your file and any special lines at the top. It builds a virtual project in a temporary folder. It compiles your code into a real program, saves the output in a cache, and then runs it. The clever part is that cache. The next time you run the same file with no changes, the SDK skips the build and runs the saved program at once.
What the SDK does on dotnet run
Steps
Read file
Scan code and directives
Build virtual project
Hidden project, no csproj
Compile
Turn code into a program
Cache
Save build in temp folder
Run
Start your script
The cache decides whether to rebuild by checking a few things: your source code, the directives you used, and the SDK version. If those match the last run, you get the fast path.
| Run | What the SDK does | Speed |
|---|---|---|
| First run | Builds, caches, then runs | Slower |
| Next run, no edits | Uses the cached program | Fast |
| After you edit the file | Rebuilds and updates cache | Slower again |
This is why a script feels a little slow the very first time and snappy every time after that.
Adding power with directives
A plain script is great, but real tools often need extra things — a NuGet package, a different SDK, or a build setting. In a normal project these live in the .csproj file. In a script, they live as special lines at the very top. These lines start with #: and are called directives.
C# 14, which shipped with .NET 10, understands the #: prefix at the language level as an "ignored directive". That means the compiler reads past it cleanly while the SDK uses it. There are a handful of directives, and here are the ones you will use most.
#:package — add a NuGet package
This is the most useful one. It pulls a library straight from NuGet, no project needed. Say you want to read a CSV file. The CsvHelper library makes that easy:
#:package CsvHelper@33.0.1
using System.Globalization;
using CsvHelper;
using var reader = new StreamReader("people.csv");
using var csv = new CsvReader(reader, CultureInfo.InvariantCulture);
var rows = csv.GetRecords<dynamic>();
var count = 0;
foreach (var row in rows)
{
count++;
}
Console.WriteLine($"The file has {count} rows.");The @33.0.1 part pins the exact version. For a tool you depend on, pinning is safer because the version will not change under you. For a quick test, you can write @* to mean "use the latest".
#:sdk — choose a different SDK
By default a script uses the plain console SDK. If you want a tiny web server in one file, switch the SDK:
#:sdk Microsoft.NET.Sdk.Web
var builder = WebApplication.CreateBuilder();
var app = builder.Build();
app.MapGet("/health", () => "OK");
app.Run();Run dotnet run server.cs and you have a working web endpoint from one file.
Other directives
You will reach for these less often, but they are good to know.
| Directive | What it does | Example |
|---|---|---|
#:package | Add a NuGet package | #:package Humanizer@2.14.1 |
#:sdk | Pick a different SDK | #:sdk Microsoft.NET.Sdk.Web |
#:property | Set a build option | #:property LangVersion=preview |
#:project | Reference a project | #:project ../Lib/Lib.csproj |
A small note about a route like GET /{id} in a web script: in plain prose you must wrap such braces in backticks, but inside your real code they are perfectly fine to type as normal.
Handy commands for scripts
The dotnet commands you already know work with a single file too. Here are the ones you will use most.
dotnet run app.cs # build and run
dotnet build app.cs # only build, do not run
dotnet clean app.cs # remove the build output
dotnet publish app.cs # make a standalone programYou pass arguments to your script after a -- separator:
dotnet run report.cs -- 2026 JuneEverything after -- reaches your code. So args[0] is 2026 and args[1] is June.
You can even run code without saving a file at all. Pipe it straight from your terminal:
echo 'Console.WriteLine(1 + 2);' | dotnet run -The lone - tells dotnet to read the code from standard input. This is great for a one-off test you do not want to keep.
Running like a real shell script
This is where C# scripts feel just like Python or Bash. On Linux and macOS, you can make your .cs file run on its own, without typing dotnet run each time.
You add a shebang line at the very top, then mark the file as executable:
#!/usr/bin/env -S dotnet --
#:package Spectre.Console@*
using Spectre.Console;
AnsiConsole.MarkupLine("[green]Backup complete![/]");Then in your terminal:
chmod +x backup.cs
./backup.csNow your C# file behaves like any other script in your system. You can drop it in a folder, put it in a cron job, or call it from another script. A small tip: use LF line endings, not Windows CRLF, and do not add a BOM, or the shebang may not work.
Turning a C# file into a shell script
Steps
Add shebang
First line points to dotnet
chmod +x
Mark the file runnable
./backup.cs
Run it like any script
A real automation example
Let me tie it all together with a small but useful tool. This script checks if a website is up and prints a friendly message. It uses no extra package at all — just the built-in HttpClient.
// healthcheck.cs
var url = args.Length > 0 ? args[0] : "https://learn.microsoft.com";
using var client = new HttpClient();
try
{
var response = await client.GetAsync(url);
if (response.IsSuccessStatusCode)
Console.WriteLine($"{url} is UP ({(int)response.StatusCode}).");
else
Console.WriteLine($"{url} returned {(int)response.StatusCode}.");
}
catch (Exception ex)
{
Console.WriteLine($"{url} is DOWN. Reason: {ex.Message}");
}Run it like this:
dotnet run healthcheck.cs -- https://www.nuget.orgNotice the await keyword at the top level. You do not need a special async Main method; top-level statements support await directly. In a dozen lines, with no project and no package, you built a tool you could run every morning.
When the script grows up
A single file is perfect for small jobs. But code has a way of growing. One day your healthcheck.cs is 20 lines. A month later it has retries, logging, and config — 500 lines. At that point a real project is easier to manage.
The good news: you do not start over. One command turns your script into a normal project.
dotnet project convert healthcheck.csThis reads your #: directives and creates a proper .csproj with matching packages and settings. Your code moves into a project folder, and from there you work exactly like any normal .NET app. The language and tools stay the same. Only the wrapping changes.
This smooth path is one of the best parts of the design. You are never trapped in the small format. You start tiny, and when you outgrow one file, you take one easy step up.
Good habits and a few limits
Scripts are powerful, but a few simple habits keep you out of trouble.
- Keep scripts in their own folder. A
.csprojnearby can change how your script builds. A clean folder avoids surprises. - Pin package versions for tools you rely on.
@*is handy for tests, but a fixed version like@33.0.1keeps a real tool stable over time. - Watch shared build files. A
Directory.Build.propsfile in a parent folder also affects your script. This is useful, but it can surprise you. - Remember it is one file in .NET 10. Splitting a script across many files of your own is planned for a later release. For now, keep each script in one file, or convert to a project when you need more.
| Great for | Not the best fit |
|---|---|
| Automation and small tools | Large multi-project solutions |
| Learning and quick demos | Big team codebases |
| One-off scripts and tests | Apps with complex build pipelines |
These are gentle guidelines, not hard rules. When a script stops feeling small, that is your sign to run dotnet project convert and move on.
Quick recap
- C# scripts let you run a single
.csfile in .NET 10 withdotnet run app.cs— no project file needed. - The SDK builds a hidden virtual project, caches it, and reuses the cache when nothing changes, so the second run is fast.
- Directives at the top add power:
#:packagefor NuGet,#:sdkfor web apps, and#:propertyfor build settings. - You pass arguments after
--, pipe code with-, and on Linux or macOS run the file like a shell script using a shebang. - Top-level
awaitand theargsarray are ready to use, which makes automation scripts short and clear. - When a script grows,
dotnet project convert app.csturns it into a normal project with no rewrite.
C# scripts make the language feel light and friendly. You can grab one file, write your idea, and run it in seconds — just like boiling a quick pot of Maggi. And when the job grows into a feast, the full kitchen is only one command away.
References and further reading
- File-based apps - Microsoft Learn — the official reference for every directive and command.
- Announcing dotnet run app.cs - .NET Blog — the original announcement and the story behind the feature.
- Build file-based apps - C# tutorial - Microsoft Learn — a hands-on, step-by-step walkthrough.
- Exploring the features of dotnet run app.cs - Andrew Lock — a careful look at how it works under the hood.
- dotnet run app.cs - codewithmukesh — a friendly community deep dive with examples.
Related Posts
Exploring C# File-Based Apps in .NET 10: Run a Single .cs File
Learn how C# file-based apps in .NET 10 let you run a single .cs file with dotnet run, add packages with directives, and grow into a full project.
How to Create Command-Line Console Applications in .NET
A beginner-friendly guide to building command-line console apps in .NET 10 with the dotnet CLI, arguments, options, and System.CommandLine.
Building File-Based Apps in .NET With Multi-File Support
Learn how to run C# without a project file using dotnet run app.cs in .NET 10, split code across files with #:include, and add packages with directives.
The New .slnx Solution Format: A Simple Migration Guide for .NET 10
Learn the new XML-based .slnx solution format in .NET 10, why it beats the old .sln file, and how to migrate safely with one CLI command.
How to Start a New .NET Project in 2026: A Beginner's Friendly Guide
A warm, step-by-step guide to starting a new .NET 10 project in 2026 with the dotnet CLI, the right template, and good folder habits from day one.
How to Write Elegant Code with C# Pattern Matching
Learn C# pattern matching the easy way. Use is, switch expressions, property and list patterns to write clean, readable, and elegant .NET code.