.NET 10: Run Standalone C# Files Without Project

.NET 10 is around the corner. One of the super cool new features of .NET 10 is that you can run standalone C# files directly without creating a project.

Running scripts is a powerful way for simpler tasks, but also for tasks that you run for example as part of your CI/CD pipeline (CI=Continuous Integration, CD=Continous Deployment). With .NET 10, you probably can use C# at many places where you used to use PowerShell or any other scripting language before.

Run a C# file

Let’s start with a simple C# file. In my local C:\Code directory, I create a Program.cs file with the content below. It should write the directories of my C: drive to the console.

var myDirectories = Directory.GetDirectories(@"C:\");
foreach (var dir in myDirectories)
{
    Console.WriteLine(dir);
}

Now let’s use the dotnet run command to run this Program.cs file directly without creating an explicit C# project. To run this command, .NET 10 or later must be installed on your machine.

dotnet run Program.cs

Below you can see a screenshot how I execute this command in a PowerShell window. As expected, it prints out the directories of my C: drive.

That’s pretty cool, isn’t it?

If you’re familiar with other programming languages that allow you to execute a file, like for example with Python, you might appreciate that this approach is now also available for C# with .NET 10.

How does it work in Python?

In Python you can execute files in a similar way. You can create for example a Program.py file with the content below that does exactly the same as the C# file from above – it lists the directories of the C: drive.

import os

for dir in os.listdir(r'C:\\'):
    path = os.path.join(r'C:\\', dir)
    if os.path.isdir(path):
        print(path)

With Python installed on your machine, you can execute the file with this command:

python Program.py

Below you can see how I executed it on my machine.

As you just learned in this blog post, with .NET 10, you can do a similar thing with your C# files.

What about NuGet Packages?

You can also reference NuGet packages in your standalone C# files with a new syntax that has this form:

#:package YourPackage@1.0.0

Let’s adjust my Program.cs file and let’s include Serilog for logging, instead of writing directly to the console via Console.WriteLine.

The code snippet below shows the full Program.cs file that uses now the NuGet packages Serilog and Serilog.Sinks.Console. After the two #:package directives, a using directive for the Serilog namespace is added, and then the static Log.Logger property is initialized with a logger that writes to the console.

In the foreach loop, the static Log.Information method is used to log a message per found directory. Note that the string contains the {Directory} placeholder, which is a Serilog specific thing. The placeholder can be any string, also just {blabla} would work. It gets replaced with the content of the dir variable. Of course, you could use an interpolated string for this too, but the result of this Serilog specific placeholder is that this part of the log message gets highlighted in the output.

#:package Serilog@4.3.0
#:package Serilog.Sinks.Console@6.0.0

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

var myDirectories = Directory.GetDirectories(@"C:\");
foreach (var dir in myDirectories)
{
    Log.Information("Found directory: {Directory}", dir);
}

Below you can see the output when you run the Program.cs file. The directories are highlighted by Serilog.

What about Project Properties?

Sometimes you specify things in a .csproj file, like for example the C# language version or a more specific .NET SDK. You can also do this with your standalone C# file by adding directives to your file that start with #:, like the NuGet package references from the previous section of this blog post that start with #:package.

To reference a specific SDK, for example the Web SDK to build a minimal API, you use the #:sdk directive. In the code snippet below you see a Program.cs file that contains the full code for a simple minimal API. Note the #:sdk directive at the top. By default, implicit usings are turned on too, which means no using directive is necessary to use the WebApplication class.

#:sdk Microsoft.NET.Sdk.Web

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello from Web API from simple C# file");

app.Run();

When you run this file like below, a web server starts up and you can consume your Web API. Isn’t this cool?

It’s also possible to specify other MSBuild properties with the #: directive. If you want to use for example the preview version of C# in your file, you can use the directive below to set the LangVersion property to the value preview:

#:property LangVersion=preview

You find more directives and even support to build executable shell scripts with C# in this .NET blog post.

What if You need a Project File?

Whenever you reach a point with your standalone C# file where you have the need for a .csproj file, you can run the following command to convert your standalone C# file to a project with a .csproj file:

dotnet project convert Program.cs

This command will ask you for an output directory for the project. Then it will create the corresponding .csproj file for you in that output directory. This .csproj file will include all NuGet package references, MSBuild properties and specified SDKs that you included in your C# file with #: directives. It will also include the C# file itself, but now without the #: directives, as all that information is now part of the .csproj file.

Let’s take the Program.cs file below that we used already in this blog post as an example.

#:package Serilog@4.3.0
#:package Serilog.Sinks.Console@6.0.0

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

var myDirectories = Directory.GetDirectories(@"C:\");
foreach (var dir in myDirectories)
{
    Log.Information("Found directory: {Directory}", dir);
}

In the console window below, I run the dotnet project convert command for the Program.cs file. I entered MyProgram as an output directory. The default name for the directory would be the name of the file, in this case Program. After that, I navigated into the MyProgram directory to list its contents with the ls command. As you can see, a Program.cs file and a Program.csproj file were created.

The created Program.csproj file contains the corresponding NuGet package references for Serilog that I defined in the C# file with the #:package directives, as you can see below.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishAot>true</PublishAot>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Serilog" Version="4.3.0" />
    <PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
  </ItemGroup>

</Project>

The Program.cs file got cleaned up and does not contain any #: directives anymore. It looks like below.

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .CreateLogger();

var myDirectories = Directory.GetDirectories(@"C:\");
foreach (var dir in myDirectories)
{
    Log.Information("Found directory: {Directory}", dir);
}

This means now, in the MyProgram directory, you can run just dotnet run like you could already with previous .NET versions, and the .NET CLI will pick up the .csproj file from that directory and execute the project respectively the generated .exe file from the project. The console window below shows this in action.

Summary

This new feature of .NET 10 to execute files directly without the need of an explicit .csproj file is very powerful. It means that C# is now a super valid choice for any script that you implemented before with PowerShell, Python or any other language.

It also means that people new to C# can start with a clean C# file, and nothing else is required. At a later stage they can dive into .csproj files.

Thanks for reading,
Thomas

Share this post

Comments (2)

  • Christa Reply

    Hi Thomas,
    you’re missing a “=” in the property example:
    #:property LangVersion preview
    Correct
    #:property LangVersion=preview
    Best regards
    Christa

    November 12, 2025 at 1:36 pm
    • Thomas Claudius Huber Reply

      Hi Christa,

      well spotted!
      You’re right, thank you.
      It’s fixed now!

      Best regards,
      Thomsa

      November 12, 2025 at 3:58 pm

Leave a Reply to Thomas Claudius Huber Cancel reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.