iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article

[C#] Running source files directly with `dotnet run file.cs` is coming

に公開

https://www.youtube.com/watch?v=98MizuB7i-w

What does this mean?

  • In .NET 10, it seems you will be able to execute C# using dotnet run file.cs.
    • It is available as of .NET SDK 10 preview 4.
dotnet run file.cs
  • Unlike dotnet run or dotnet build, /bin and /obj folders are not generated.

    • No build artifacts or intermediate products are created.
  • You can later convert it to the standard source + project file format.

    • dotnet project convert file.cs
  • New syntax seems to be coming in C# 14.0?

ignored directives
#!/usr/bin/dotnet run
#:sdk      Microsoft.NET.Sdk.Web
#:property TargetFramework net10.0
#:property LangVersion preview
#:package  System.CommandLine 2.0.0-*

Console.WriteLine("Hello, World!");
  • This is different from the long-standing "C# Script (the ones with the *.csx extension)."

dotnet run file.cs

The conventional dotnet run

The existing dotnet run command is a convenient tool that handles dependency resolution, compilation, and execution for C#[1] all at once.

https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-run

While C# is strongly associated with sharing compiled executable files, the dotnet run command allows you to distribute and use source code directly, similar to script distribution (as long as the SDK is installed in the environment).

However, until now, the dotnet run command required a project file (*.csproj) in addition to the source file (*.cs).

Even though dotnet new console creates it automatically from a template, having multiple files is inconvenient for script-like usage.

Directly Specifying Source Files (*.cs)

In .NET 10 and later, the dotnet run command will allow you to execute by specifying a source file. A project file (*.csproj) is not required[2].

dotnet run file.cs

The way to specify command-line arguments is the same as dotnet run; arguments other than the source file specification are passed to args.

Options can also be passed
dotnet run file.cs --option=xxx

If no source file is specified, it behaves as before (searching for and using a project file).

No Build Artifacts or Intermediate Products (bin/obj) are Generated

In regular dotnet run or dotnet build, build results (like .dll or .exe) and intermediate artifacts are generated in /bin and /obj within the same directory.

However, with this dotnet run file.cs, no build results or intermediate products are generated.
Neither an .exe nor a .dll exists; it runs directly. It feels a bit like a script.

That said, it's not that it isn't being built; the first run takes a fair amount of time because it also checks dependencies (similar to when you run dotnet run for the first time). Unfortunately, it's not as fast as a script!

Since /bin and /obj are not created, it also prevents mistakes like accidentally committing them because you forgot to specify them in .gitignore[4]!

Which Source Files Can Be Specified?

The source files that can be executed are files that can serve as entry points.

Speaking of entry points, since modern C# has Top-level statements, essentially any source file where code is written directly like a script will serve as an entry point.

This alone is an entry point (file.cs)
Console.WriteLine("Hello, World!");

If you specify a .cs file that is not an entry point, it will fail.

Example: dotnet run lib.cs
CSC : error CS5001:
Program does not contain a static 'Main' method suitable for an entry point
Build of project "lib.csproj" finished -- FAILED.

The build failed. Fix the build errors and run again.
Example lib.cs
// This is not an entry point
public class Lib {}

New C# Syntax: ignored directives

New syntax is planned to be added to C# as well.

To be precise, there are two types, and lines starting with the following combinations of symbols are ignored as C# code.

Written at the beginning of the file
#!/usr/bin/dotnet run
#:sdk      Microsoft.NET.Sdk.Web
#:property TargetFramework net11.0
#:property LangVersion preview
#:package  System.CommandLine 2.0.0-*

Console.WriteLine("Hello, World!");

No characters can be written before these lines (BOM is also not allowed).

badignore.cs If there is a blank line...

#!/usr/bin/dotnet run
#:sdk      Microsoft.NET.Sdk.Web
#:property TargetFramework net11.0
#:property LangVersion preview
#:package  System.CommandLine 2.0.0-*

Console.WriteLine("Hello, World!");
Error
error CS1040: Preprocessor directives must appear as the first non-whitespace character on a line
Build of project "badignore.csproj" finished -- FAILED.

The build failed. Fix the build errors and run again.

These notations have no meaning as C# code and are intended to provide information to the compiler, MSBuild, etc. By writing the contents previously described in the .csproj here, the .csproj becomes unnecessary.

#! (shebang)

You are likely familiar with the shebang directive from shell scripts and the like.

#!/usr/bin/dotnet run

https://en.wikipedia.org/wiki/Shebang_(Unix)

In other words, by specifying the path in a Unix environment and using chmod, you can execute the .cs file on its own.

[Update] For macOS Sequoia 15.5

#!/usr/bin/dotnet run does not work.
Rewrite the first line in the sample source as follows:

file.cs
- #!/usr/bin/dotnet run
+ #!/usr/bin/env -S dotnet run

After that, if you grant execution permissions (chmod 755), you can run it just by specifying the .cs file.

% chmod 755 file.cs
% ./file.cs
Hello, World!

#:

In terms of syntax, only #: at the beginning of the line has been added.
However, in practice, you will write the contents that were previously in the .csproj, such as #:sdk or #:property, following the #:.

Syntax .csproj
#:sdk *** <Project Sdk="***">
#:property XX YY <PropertyGroup><XX>YY</XX></PropertyGroup>
#:package XX 1.0.0-* <ItemGroup><PackageReference Include="XX" Version="2.0.0-*" /></ItemGroup>

Currently, it seems that only three options can be specified: sdk, property, and package.

Can be converted to a standard .csproj

You can convert it to a standard format consisting of a regular source file (*.cs) and a project file (*.csproj) using the following command:

dotnet project convert file.cs
Before conversion
file.cs

After conversion
file/
 ├ file.cs
 └ file.csproj

The sections containing ignored directives are removed from the source file and converted into the .csproj file.

Directives before conversion
#!/usr/bin/dotnet run
#:sdk      Microsoft.NET.Sdk.Web
#:property TargetFramework net11.0
#:property LangVersion preview
#:package  System.CommandLine 2.0.0-*

file.csproj
<Project Sdk="Microsoft.NET.Sdk.Web">

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

  <PropertyGroup>
    <TargetFramework>net11.0</TargetFramework>
    <LangVersion>preview</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.CommandLine" Version="2.0.0-*" />
  </ItemGroup>

</Project>

How is it different from C# Script / REPL?

C# has long had a script execution mechanism called C# Script (Roslyn Script)[6].

https://zenn.dev/zead/articles/first-polyglots

As the name implies, C# Script is for script execution, so it has the following differences from this new feature:

On the other hand, the C# in dotnet run file.cs is standard C# source code.
You don't need to write ignored directives if you don't need them; you can use existing C# source code as-is.

Since building takes a bit of time, it doesn't run quite as fast as a script[7], making it less suitable for REPL use.

Other Trivia

  • Although the documentation states that dotnet restore file.cs and dotnet build file.cs are also possible, they did not work in .NET SDK 10.0-preview 4.
    • Will they be included in the final release?
  • There are no plans to support dotnet pack file.cs or dotnet watch run file.cs.
    • I feel like people might eventually want watch support...
  • It seems only files with the *.cs extension are supported[8].
    • There might be no intention to support F# or VB.NET.
      • Then again, F# already has dotnet fsi, so it might not be necessary.
  • The option to output binary logs with -bl appears to be enabled.
  • Update: Improvements to build performance are being considered.
  • Support for execution via dotnet file.cs (omitting the explicit run subcommand) is also under consideration.

Summary

  • (Probably) In .NET 10, you will be able to run C# from a single file using dotnet run file.cs, making project files (.csproj) unnecessary.
  • Build artifacts and intermediate files (/bin and /obj) are not generated.
  • Execution is the same as regular C# code (not a script), so existing C# knowledge can be applied directly.
  • Newly introduced #: and #! directive syntax allows for specifying dependencies and the SDK within the .cs file.
  • Can be converted to a regular project structure using dotnet project convert file.cs.
  • It is different from existing C# Script (.csx).
  • Currently[9], available in the preview version of .NET SDK 10.

Update

https://zenn.dev/inuinu/articles/before-csharp13-dotnet-run-file

References

脚注
  1. More accurately, F# and others as well ↩︎

  2. Internally, it behaves as if a virtual project file is created ↩︎

  3. Build outputs ↩︎

  4. dotnet new gitignore would solve that anyway, though... ↩︎

  5. Currently not implemented as of preview 4 Support multiple files in file-based programs by jjonescz · Pull Request #48782 · dotnet/sdk ↩︎

  6. Note that while it's confusing, Unity's C# is also called "C# Script," but that is something else entirely ↩︎

  7. That said, dotnet-script also takes about the same time for the first run ↩︎

  8. Add basic support for 'dotnet run file.cs' #46915 ↩︎

  9. 2025-05-23 ↩︎

Discussion