Skip to main content
SEMastery
.NET Corebeginner

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.

11 min readUpdated April 30, 2026

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.cs

That 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.

The old way needed a full project. The new way runs a single file.

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

Read
Build
Restore
Compile
Run

Steps

1

Read

Read app.cs and any directives

2

Build

Create a virtual project in memory

3

Restore

Download any NuGet packages

4

Compile

Turn C# into IL

5

Run

Execute your program

Each step happens automatically when you run a file.

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:

DirectiveWhat it doesExample
#:packageAdds a NuGet package#:package Humanizer@2.14.1
#:includePulls in another .cs file#:include helpers.cs
#:projectReferences another project#:project ../Core/Core.csproj
#:propertySets an MSBuild property#:property LangVersion=preview
#:sdkImports 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 main file includes smaller helper 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.

FileTop-level statements?What goes inside
Main file (app.cs)Yes — this is the entry pointYour script logic + directives
Included file (math.cs)NoClasses, methods, records, enums
Included file (greetings.cs)NoHelper 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.cs

The 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

Main
Includes
Gather
Compile
Output

Steps

1

Main

app.cs lists #:include lines

2

Includes

math.cs and greetings.cs are found

3

Gather

All files join one virtual project

4

Compile

Compiled together as a unit

5

Output

Program runs and prints

The main file gathers helpers, then everything compiles as one.

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.

The journey of file-based apps across versions.

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.cs

This 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 situationBest choice
Quick script, a few linesSingle .cs file
Script with helper classes#:include extra files
Needs many folders, tests, a teamConvert 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 7

You 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

Quick recap

  • A file-based app runs C# straight from a single .cs file with dotnet 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 #:include to 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 #:include experience; richer multi-file support is shaped for .NET 11.
  • When your script grows, run dotnet project convert app.cs to 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