Skip to main content
SEMastery
.NET Corebeginner

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.

11 min readUpdated April 1, 2026

Imagine you want to write a short note to a friend. You do not buy a notebook, punch holes in the pages, and bind them together first. You just grab one piece of paper and start writing. If the note grows into a long story, then you bind the pages into a book.

For years, writing C# felt like you always had to bind the book first. Before you could run even one line, you needed a project file, folders, and some setup. That was a lot of work just to try a small idea.

.NET 10 changes this. Now you can grab "one piece of paper" — a single .cs file — and run it right away. This feature is called file-based apps. In this post you will learn what they are, how they work, and when to use them, all in simple steps.

What is a file-based app?

A file-based app is a C# program that lives in one file and runs without a project file. You write your code in something like hello.cs, and then you run it:

// hello.cs
Console.WriteLine("Hello from a single file!");
dotnet run hello.cs

That is it. No .csproj. No solution. No Program.cs inside a folder. Just one file and one command.

This works because of top-level statements, a C# feature that lets you skip the class Program and static void Main wrapper. The code you see above is the whole program. The .NET SDK reads your file, quietly builds a hidden project around it, and runs the result.

Why this is a big deal

Before .NET 10, the smallest "real" C# program still needed a project. People who came from Python or JavaScript found this strange. In those languages you can save one file and run it. Now C# can do the same thing. This makes C# friendlier for:

  • Learning — a student can try ideas without setup.
  • Prototyping — you can test a small idea in seconds.
  • Automation scripts — small helper tools that do one job.
The old way needed a whole project. The new way starts with one file.

How it runs behind the scenes

When you type dotnet run hello.cs, a few things happen that you never see. Understanding them helps you trust the tool.

First, the SDK reads your file and any special lines at the top (we will meet those soon). It then builds a virtual project in a temporary folder. It compiles your code into a real program and runs it. The build output is stored in a cache folder under your system's temp directory.

The clever part is the cache. The next time you run the same file, if nothing changed, the SDK skips the build and runs the saved program straight away. This makes the second run much faster.

What happens on dotnet run

Read file
Build virtual project
Compile
Cache
Run

Steps

1

Read file

Scan code and #: directives

2

Build virtual project

SDK makes a hidden project

3

Compile

Turn code into a program

4

Cache

Save output in temp folder

5

Run

Start your app

The steps the SDK takes for a file-based app.

The cache decides whether to rebuild based on a few things: your source file content, the directives you used, the SDK version, and any shared build files nearby. If all of those are the same as last time, you get the fast path.

Run numberWhat the SDK doesSpeed
First runBuilds, caches, then runsSlower
Next run (no changes)Uses cached programFast
After you edit the fileRebuilds, updates cacheSlower again

Adding power with directives

A plain file is nice, but real programs often need extra things: a NuGet package, a different SDK, or a build setting. In a normal project these live in the .csproj. In a file-based app, they live as special lines at the top of your file. These lines start with #: and are called directives.

There are five of them. Let me show each one simply.

#:package — add a NuGet package

This pulls in a library from NuGet. For example, Spectre.Console makes colourful console output.

#:package Spectre.Console@*
 
using Spectre.Console;
 
AnsiConsole.MarkupLine("[green]Hello[/], [yellow]World[/]!");

The @* means "use the latest version". You can also pin a version, like #:package Serilog@3.1.1, which is safer for real tools because the version will not change under you.

#:sdk — choose a different SDK

By default a file-based app uses Microsoft.NET.Sdk, which is for console programs. If you want a tiny web API, switch the SDK:

#:sdk Microsoft.NET.Sdk.Web
 
var builder = WebApplication.CreateBuilder();
var app = builder.Build();
 
app.MapGet("/", () => "Hello from a single-file web API!");
 
app.Run();

Run it with dotnet run api.cs and you have a working web server from one file. This is the same minimal API style you would use in a full ASP.NET Core project.

#:property — set a build setting

This sets an MSBuild property, which is just a build option. For example, you can turn off Native AOT publishing:

#:property PublishAot=false

#:project — reference another project

If your script needs code from a real project nearby, point to it:

#:project ../SharedLibrary/SharedLibrary.csproj

#:include — bring in another file

Newer SDK versions let you split helper code into another file and include it:

#:include helpers.cs

Here is a quick table you can keep as a cheat sheet.

DirectiveWhat it doesExample
#:packageAdd a NuGet package#:package Newtonsoft.Json@13.0.3
#:sdkPick a different SDK#:sdk Microsoft.NET.Sdk.Web
#:propertySet a build option#:property PublishAot=false
#:projectReference a project#:project ../Lib/Lib.csproj
#:includeAdd another file#:include models.cs
Directives at the top of the file replace what a .csproj would normally hold.

Useful CLI commands

The same dotnet commands you already know work with a single file. Here are the handy ones.

dotnet run app.cs          # build and run
dotnet build app.cs        # only build
dotnet clean app.cs        # remove build output
dotnet publish app.cs      # make a standalone program
dotnet pack app.cs         # package as a .NET tool

You can pass arguments to your program after a -- separator:

dotnet run app.cs -- hello world

Everything after -- goes to your app, not to dotnet. So inside your code, args[0] would be hello and args[1] would be world.

You can even pipe code straight from your terminal without saving a file at all:

echo 'Console.WriteLine("hi from stdin");' | dotnet run -

The lone - tells dotnet to read the code from standard input. This is great for very quick one-off tests.

Running like a real script (shebang)

On Linux and macOS you can make your file run like any other script. 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]Hello, World![/]");

Then in your terminal:

chmod +x app.cs
./app.cs

Now your C# file behaves like a shell or Python script. A small note: use LF line endings (not Windows CRLF) and do not add a BOM, or the shebang may not work. The -S flag and the -- separator make sure your own arguments reach your app and are not swallowed by dotnet.

Running a C# file as a shell script

Add shebang
chmod +x
./app.cs

Steps

1

Add shebang

#!/usr/bin/env -S dotnet --

2

chmod +x

Mark file executable

3

./app.cs

Run it directly

Three small steps to treat a .cs file like any script on Unix.

When the file grows: convert to a project

A single file is perfect for small things. But programs have a way of growing. One day your app.cs is 30 lines; a month later it is 800 lines with many features. At that point a real project is easier to manage.

The good news: you do not start over. One command turns your file into a normal project.

dotnet project convert app.cs

This reads your #: directives and creates a proper .csproj with matching settings, packages, and properties. Your original file is left untouched, and a new project folder appears next to it. After that, you work exactly like any normal .NET project. The language and tools stay the same the whole way — only the wrapping changes.

A file-based app can grow smoothly into a full project when it gets bigger.

This smooth path is one of the best parts of the design. You are never stuck. You start small, and when you outgrow one file, you take one step up. There is no rewrite and no new language to learn.

A complete example

Let me tie it together with one small but real program. This script asks NuGet's Humanizer library to turn a number of seconds into friendly text.

#:package Humanizer@2.14.1
 
using Humanizer;
 
var seconds = args.Length > 0 ? int.Parse(args[0]) : 4000;
var friendly = TimeSpan.FromSeconds(seconds).Humanize();
 
Console.WriteLine($"{seconds} seconds is about {friendly}.");

Run it like this:

dotnet run friendly.cs -- 9000

You will see something like 9000 seconds is about 2 hours. In just a few lines, with one package and no project file, you built a useful little tool.

Good habits and a few limits

File-based apps are powerful, but a few simple habits keep you out of trouble.

  • Do not put a script inside a project folder. A .csproj nearby can change how your script builds. Keep scripts in their own folder.
  • Pin package versions for tools you depend on. @* is handy for quick tests, but a fixed version like @13.0.3 keeps a tool stable over time.
  • Watch shared build files. Files like Directory.Build.props in a parent folder affect your script too. This is useful, but it can surprise you.
  • Mind the cache with parallel runs. Running many copies of the same file at once can clash over build files. If you need that, build once with dotnet build app.cs, then run with --no-build.
Best forNot the best fit
Small scripts and toolsLarge multi-project solutions
Learning and demosBig team codebases with many files
Quick automation tasksApps needing complex build pipelines

These are not hard rules, just gentle guidance. When a script stops feeling small, that is your sign to run dotnet project convert and move on to a full project.

Quick recap

  • File-based apps let you run a single .cs file in .NET 10 with dotnet run app.cs — no project file needed.
  • The SDK builds a hidden virtual project, caches it, and reuses the cache when nothing changes.
  • Directives at the top add power: #:package, #:sdk, #:property, #:project, and #:include.
  • You can pass arguments after --, pipe code from stdin with -, and on Unix run the file like a script using a shebang.
  • When your file grows, dotnet project convert app.cs turns it into a normal project with no rewrite.
  • Keep scripts in their own folder, pin versions for stable tools, and watch shared build files.

File-based apps make C# feel light and friendly again. You can start with one piece of paper, write your idea, and only bind the book when the story gets long. That is a very kind way to learn and build.

References and further reading

Related Posts