Hello .NET run file, goodbye csproj

Hello .NET run file, goodbye csproj

This article introduces the new file-based program feature of the .NET CLI, which allows developers to run C# source files directly without creating a project file. This feature works by generating a virtual project file in memory and supports NuGet dependencies and project property settings, providing convenience for developing scripts and simple applications. The article also looks forward to the future development directions of this feature, including target path extension, unified command-line arguments, performance improvements, and support for more file-based program commands.

Last updated 5/24/2025 9:34 PM
WeihanLi amazingdotnet
8 min read
Category
.NET
Tags
.NET C# NuGet Script

Intro

.NET is constantly evolving to make the development experience simpler and more efficient. Recently, the .NET CLI (command-line tool) introduced an exciting new feature: the ability to run C# source files directly without needing a project file. This feature is called File-based Programs, and .NET 10 will add support for dotnet run file. Currently available in .NET 10 Preview 4, you can download the latest .NET 10 SDK to try it out. Some of the functionality from my own dotnet-exec project can now be handled by the native SDK support.

What

Traditional .NET application development always requires a project file (.csproj), even just to run a simple piece of code. File-based programs remove this barrier, allowing you to run any C# file directly from the command line:

echo 'Console.WriteLine("Hello C#!");' > hello.cs
dotnet run hello.cs

No project file, no complex configuration—write C# like a script!

dotnet run hello.cs

How it works

  • When you execute dotnet run file.cs, the CLI automatically generates an "implicit project" (a virtual project file) in memory for that file. This behaves exactly as if you had created a project based on the dotnet new console template. This ensures that when you use a single-file script, the behavior is consistent with developing with a standard project. Its implementation is very similar to the project compiler in dotnet-exec, but better—it creates a virtual project file without actually creating any files on disk.
  • All .cs files (including those in subdirectories) under the target file's directory will be compiled (e.g., common utility classes, resources).
  • Relevant build files (such as Directory.Build.props) are automatically applied.
  • If the file you run has no entry point method, an error will be shown to prevent accidental execution, as illustrated below:

dotnet run file error

More Usage

  • To support the expansion of file-based programs, a new #: directive has been introduced. These directives are automatically ignored when compiling a project file, but for file-based programs they are parsed and converted into the SDK, references, and project property settings in the project file.

  • Using special directives at the top of the file (e.g., #:sdk Microsoft.NET.Sdk.Web, #:package, #:property), you can declare NuGet dependencies or project properties. For example:

    #:sdk Microsoft.NET.Sdk.Web
    #:package System.CommandLine@2.0.0-*
    #:property TargetFramework net10.0
    

    You can specify #:sdk Microsoft.NET.Sdk.Web to create a single-file WebApplication:

    #:sdk Microsoft.NET.Sdk.Web
    
    var app = WebApplication.Create(args);
    app.MapGet("/", () => "Hello World!");
    await app.RunAsync();
    

    Run our single-file webapi with dotnet run webapi.cs:

    dotnet run webapi.cs

    Access our API:

    webapi test

    We can also reference NuGet packages and configure properties, as shown below:

    #:sdk Microsoft.NET.Sdk.Web
    #:package WeihanLi.Web.Extensions@2.1.0
    #:property ManagePackageVersionsCentrally false
    
    using WeihanLi.Web.Extensions;
    
    var app = WebApplication.Create(args);
    app.MapGet("/", () => "Hello World!");
    app.MapRuntimeInfo();
    await app.RunAsync();
    

    Similarly, run with dotnet run webapi.cs:

    webapi runtime-info sample

  • When your script becomes more complex and you want more customization without using file-based programs, you can convert it to a standard project with a single command. The CLI will automatically generate a .csproj file and migrate the relevant configuration, with no change in code behavior:

    dotnet project convert webapi.cs
    

    The converted project will look like this, and you can also run it directly with dotnet run:

    converted project info

  • Supports shebang (#!) on Linux/Unix, allowing C# scripts to be run directly like Bash/Python. You can execute ./hello.cs:

    #!/usr/bin/dotnet run
    Console.WriteLine("Hello, .NET!");
    

    shebang setting

Future Features

The proposal to run C# files directly with dotnet run file.cs brings flexible and powerful scripting to .NET. The design document also looks ahead to many possible future enhancements that will make file-based programs even more practical in the .NET ecosystem. Some future features include:

1. Expanded Target Path Support

  • Folder as target: Allow folders to be used as targets for dotnet run (e.g., dotnet run ./my-app/), running the main entry point in that folder.
  • Standard input and inline code: Support dotnet run --cs-from-stdin to read C# code from standard input, or dotnet run --cs-code 'Console.WriteLine("Hi")' for raw code scripting.

2. Unified Command-Line Arguments

  • Directory and file options: Introduce --directory, --file, or a unified --path option to make the command experience consistent for both file-based and project-based programs.
  • Multiple entry point support: Add parameters like --entry to easily choose a specific entry point in multi-program directories.

3. Enhanced Integration and Consistency

  • Seamless experience before and after growth: Ensure commands like dotnet run work smoothly whether in file-based form or after upgrading to a project.
  • Common options: In the future, a unified parameter that works for both project and file formats could make switching between formats smoother.

4. Performance and Usability Improvements

  • Selective file inclusion: Use parameters or directives to control which files are included, reducing the trouble and performance overhead of accidentally including many files.
  • Nested file error hints: If a subdirectory contains too many .cs files or .csproj files, consider showing errors or warnings to avoid accidentally compiling a large amount of content.

5. Optimization for Multi-Entry Point Scenarios

  • Project structure generation: Optimize the structure generated when a multi-entry point directory "grows" into a project, with clear subdirectory division and cleaner shared code handling.
  • Advanced access control: Explore features like InternalsVisibleTo for better code sharing, and consider future enhancements to the C# language.

6. Shebang and Shell Support

  • Dedicated executable: Might release a dotnet-run or dotnet-run-file executable to improve shebang (#!) compatibility across different shell environments, enabling scripts to run directly cross-platform.
  • Support for /usr/bin/env: Investigate how to work around the parameter-passing limitations of shebang in shells, improving CLI script usability.

7. More File-Based Program Command Support

  • Build and restore: Extend commands like dotnet restore file.cs and dotnet build file.cs to enable IDE and CI integration for file-based programs.
  • Package management: dotnet package add could directly add #:package directives to the top of C# files, simplifying script dependency management.

8. Explicit File Imports

  • Import directive: Future support for directives like #import ./another-file.cs to explicitly specify which C# files to include, giving developers more control.

More

Currently we still need to use dotnet run hello.cs, but in the future it may be simplified to dotnet hello.cs and even support executing raw code. Looking forward to even more useful features later~~

For more information, refer to the official documentation: https://github.com/dotnet/sdk/blob/main/documentation/general/dotnet-run-file.md

One slight imperfection is that this introduces yet another syntax for referencing NuGet packages, which is different from the nuget: package@version syntax in dotnet-script and not fully compatible with dotnet-script.

Custom entry points are not yet supported; if we could customize the entry method in the future, that would be even better. Looking forward to it~~

Finally, at Build there was an introduction video about dotnet run file – if you're interested, you can check it out.

[Click the original WeChat post to watch the video]

References

Keep Exploring

Related Reading

More Articles