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.
A lunch dabba you can grab and go
Think about a tiffin (a dabba) you carry to school or work. When you only need one chapati and a little sabzi, you do not pull out the big steel multi-box tiffin. You grab one small box, put the food in, and go. It is quick. No fuss.
But some days you carry rice, dal, sabzi, and a sweet. Now one small box is not enough. You snap a few boxes together into one stacked tiffin. Same idea, just a little bigger.
C# in .NET 10 now works the same way. For a small program you use one file and run it. When that file grows, you snap on a few more files. No heavy setup until you actually need it. This is what we call file-based apps, and this guide will walk you through them step by step, slowly and simply.
What is a file-based app?
For a long time, the smallest C# program still needed a project. You ran dotnet new console, it made a folder, a .csproj file, and a Program.cs. That is fine for real apps. But for a 10-line script, it felt like bringing the big tiffin for one chapati.
A file-based app removes that step. You write your code in a single .cs file and run it directly:
// hello.cs
Console.WriteLine("Hello from a single file!");
var name = args.Length > 0 ? args[0] : "friend";
Console.WriteLine($"Welcome, {name} 👋");Now run it from the terminal:
dotnet run hello.csThat is it. No project file. No extra folder. The .NET SDK reads your file, builds a small project in memory, and runs it. Languages like Python and Node.js have always let you do python app.py or node app.js. With .NET 10, C# finally feels that light too.
How does it work behind the scenes?
When you type dotnet run app.cs, the SDK does not magically skip the build. C# still needs to be compiled. What changes is that you do not have to create or see the project.
Behind the curtain, the SDK creates a virtual project — a project that lives only in memory. It points that project at your file, restores any packages, compiles, and runs. You never see a .csproj unless you ask for one.
What the SDK does for you
Steps
Read
Read app.cs and any directives
Build
Create a virtual project in memory
Restore
Download any NuGet packages
Compile
Turn C# into IL
Run
Execute your program
Because the project is virtual, the first run might feel a touch slow (it restores and compiles). Later runs are faster because the SDK caches the result. So do not worry if the very first dotnet run app.cs takes a second or two.
Special directives at the top of the file
Here is the clever part. A normal .csproj file lets you add packages, set properties, and reference SDKs. A file-based app has no .csproj, so where do those settings go? They go right inside your .cs file, as special comment-like lines that start with #:.
These lines are called directives. They are not real C# code — the SDK reads them first and uses them to build your virtual project. Here are the ones you can use in .NET 10:
| Directive | What it does | Example |
|---|---|---|
#:package | Adds a NuGet package | #:package Humanizer@2.14.1 |
#:include | Pulls in another .cs file | #:include helpers.cs |
#:project | References another project | #:project ../Core/Core.csproj |
#:property | Sets an MSBuild property | #:property LangVersion=preview |
#:sdk | Imports a .NET SDK | #:sdk Microsoft.NET.Sdk.Web |
All directives must sit at the top of the file, before your top-level statements begin. Think of them as the label on your tiffin that says what is inside.
Adding a package
Let us make our greeting friendlier using the popular Humanizer package. Notice the single line at the top:
// greet.cs
#:package Humanizer@2.14.1
using Humanizer;
int days = 3;
Console.WriteLine($"See you in {days} {"day".ToQuantity(days)}!");
Console.WriteLine("welcome_to_dotnet".Humanize());Run it the same way with dotnet run greet.cs. The SDK sees the #:package line, downloads Humanizer, and then runs your code. You never touched a project file.
Now the main event: multi-file support
A single file is great until your script grows. Maybe you have a few helper methods, a small class or two, and your main file is getting long. You do not want one giant file. You also do not want to jump to a full project yet.
This is where #:include shines. It lets you snap on more boxes to your tiffin. You keep one main file with the top-level statements, and you move helpers into other .cs files.
One important rule about included files
Your main file is allowed to have top-level statements — the loose lines of code that are not inside any method, like Console.WriteLine(...). This is your program's starting point.
The files you include are different. They can hold classes, methods, records, and namespaces. But they cannot have their own top-level statements. Only one file (the main one) is allowed to be the entry point. If you forget this rule, the compiler will gently complain.
| File | Top-level statements? | What goes inside |
|---|---|---|
Main file (app.cs) | Yes — this is the entry point | Your script logic + directives |
Included file (math.cs) | No | Classes, methods, records, enums |
Included file (greetings.cs) | No | Helper types and functions |
A full multi-file example
Let us build a tiny program split across three files. First, the helper file with some math:
// math.cs
public static class MathHelpers
{
public static int Add(int a, int b) => a + b;
public static int Factorial(int n)
{
int result = 1;
for (int i = 2; i <= n; i++)
result *= i;
return result;
}
}Next, a helper that builds friendly messages:
// greetings.cs
public static class Greetings
{
public static string ForStudent(string name) =>
$"Hello {name}, ready to learn some C#?";
}Finally, the main file that pulls them in and uses them:
// app.cs
#:include math.cs
#:include greetings.cs
Console.WriteLine(Greetings.ForStudent("Anaya"));
Console.WriteLine($"3 + 4 = {MathHelpers.Add(3, 4)}");
Console.WriteLine($"5! = {MathHelpers.Factorial(5)}");Run only the main file:
dotnet run app.csThe SDK reads the two #:include lines, gathers all three files into the virtual project, compiles them together, and runs the result. You get clean, separated code — and still no project file.
How #:include builds your program
Steps
Main
app.cs lists #:include lines
Includes
math.cs and greetings.cs are found
Gather
All files join one virtual project
Compile
Compiled together as a unit
Output
Program runs and prints
What about .NET 11?
It helps to know where this is heading. In .NET 10, the file-based experience is built around one main file that can pull in helpers with #:include. The .NET team chose to make this single-file path really solid first.
Fuller multi-file support — where several files behave more like a normal project without you writing directives — is being shaped for .NET 11. So #:include is the tool you use today, and it already handles most everyday scripts comfortably. Remember that .NET 10 is the current LTS (long-term support) release and C# 14 has shipped, so this is stable ground to build on, not a risky preview.
When your script grows up: converting to a project
A file-based app is perfect while it is small. But one day your handy script becomes a real tool. It needs lots of files, folders, tests, and maybe a team. That is the moment to graduate it into a normal project.
You do not rewrite anything by hand. The SDK has a command for exactly this:
dotnet project convert app.csThis reads your file (and its directives), creates a proper .csproj, and moves your code into a project folder. Your #:package lines become <PackageReference> entries, your #:property lines become MSBuild properties, and so on. Nothing is lost — you simply move from the small tiffin to the big one.
Here is a simple way to decide:
| Your situation | Best choice |
|---|---|
| Quick script, a few lines | Single .cs file |
| Script with helper classes | #:include extra files |
| Needs many folders, tests, a team | Convert to a .csproj project |
A real, slightly bigger example
Let us tie it together with something that feels real: a tiny tool that reads a number from the command line and prints a small report. We will split the report logic into its own file.
// report.cs
public static class Report
{
public static string Build(int number)
{
var lines = new List<string>
{
$"You entered: {number}",
$"Doubled: {number * 2}",
$"Is even? {(number % 2 == 0 ? "Yes" : "No")}",
$"Square: {number * number}"
};
return string.Join(Environment.NewLine, lines);
}
}And the main file that reads the input and prints the report:
// main.cs
#:include report.cs
if (args.Length == 0 || !int.TryParse(args[0], out var n))
{
Console.WriteLine("Please pass a whole number. Example: dotnet run main.cs 7");
return;
}
Console.WriteLine(Report.Build(n));Run it with an argument:
dotnet run main.cs 7You now have a clean two-file program that reads input, validates it, and prints a result — all without a project file. When it grows, dotnet project convert main.cs is one command away.
Why students and beginners love this
A quick word on why this matters if you are just starting out. The old project setup, with its folders and XML files, can feel scary on day one. File-based apps remove that wall. You open one file, type some C#, and run it. The feeling is the same joy a beginner gets from print("hello") in Python — but you are writing real, fast C#.
It is also wonderful for:
- Learning a new idea quickly without ceremony.
- Trying a NuGet package in seconds with
#:package. - Sharing a small fix as a single file that a friend can run.
- Automation scripts that do one job well.
When the idea proves itself, you grow it into a full project. Start small, grow when ready.
References and further reading
- Announcing dotnet run app.cs — .NET Blog
- File-based apps — Microsoft Learn
- Build file-based apps — C# tutorial (Microsoft Learn)
- Exploring C# file-based apps in .NET 10 — Milan Jovanović
- Exploring dotnet run app.cs — Andrew Lock
- dotnet run app.cs — codewithmukesh
Quick recap
- A file-based app runs C# straight from a single
.csfile withdotnet run app.cs— no project file needed. - Behind the scenes, the SDK builds a virtual project in memory, restores packages, compiles, and runs.
- Directives that start with
#:go at the top of the file. They add packages, set properties, and more. - Use
#:includeto split your code across files. The main file has the top-level statements; included files hold classes and methods only. - .NET 10 focuses on the single-file plus
#:includeexperience; richer multi-file support is shaped for .NET 11. - When your script grows, run
dotnet project convert app.csto turn it into a full project without losing anything. - Start small like a single-box tiffin, and snap on more boxes only when you truly need them.
Related Posts
C# 15 Union Types: The Easy Guide With Real Examples
Union types in C# 15 (.NET 11) let one method safely return one of many shapes. Learn how they work with simple, real-life examples, diagrams, and a clear comparison with OneOf.
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.
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.
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.
How to Apply Functional Programming in C#: A Beginner's Guide
Learn functional programming in C# the simple way: pure functions, immutability, records, LINQ, pattern matching, and composition with friendly examples.
New Features in C# 13: A Friendly Beginner's Guide
Learn the new features in C# 13 with simple words, real-life examples, diagrams, and code you can read in minutes. Great for beginners.