npx skills add ...
npx skills add dotnet/skills --skill csharp-scripts
Run file-based C# apps with the .NET CLI when the user explicitly wants C#/.NET code without creating a project. Use for C# language/API experiments, one-file C# apps, small multi-file C# apps composed with `#:include`/`#:exclude`, or C# file-based apps linked with `#:ref`. Do not use for language-agnostic throwaway scripts, generic computations, Python/PowerShell-style automation, full projects, or existing app integration.
npx skills add dotnet/skills --skill csharp-scripts
.cs files.csproj| Input | Required | Description |
|---|---|---|
| C# code or intent | Yes | The code to run, or a description of what the file-based app should do |
Run dotnet --version to verify the SDK is installed and note the full version, including the feature band. File-based apps require .NET 10 or later. #:include, #:exclude, and transitive directive processing require SDK 10.0.300 or later; SDK 10.0.100/10.0.200 builds can run single-file apps but do not support those multi-file directives. If the version is below 10, follow the fallback for older SDKs instead.
Create an entry-point .cs file using top-level statements. Place it outside any existing project directory to avoid conflicts with .csproj files.
Guidelines:
Main method, class, or namespace boilerplate)using directives at the top of the file (after the #! line and any #: directives if present)Builds and runs the file automatically. Cached so subsequent runs are fast. Pass arguments after --:
Place directives at the top of the file (immediately after an optional shebang line), before any using directives or other C# code. All directives start with #:.
#:package — NuGet package referencesSpecify a version unless the app intentionally uses central package management. Use @* when the latest available package is acceptable (or @*-* for pre-release):
#:property — MSBuild propertiesSet any MSBuild property inline. Syntax: #:property PropertyName=Value
MSBuild expressions and property functions are supported:
Common properties:
| Property | Purpose |
|---|---|
AllowUnsafeBlocks=true | Enable unsafe code |
PublishAot=false | Disable native AOT (enabled by default) |
NoWarn=CS0162;CS0219 | Suppress specific warnings |
LangVersion=preview | Enable preview language features |
InvariantGlobalization=false | Enable culture-specific globalization |
#:project — Project referencesReference another project by relative path:
#:ref — File-based app referencesReference another .cs file as a separate file-based app project when it should compile into a separate assembly instead of being included in the same compilation. Use #:include for ordinary helper files that should share the same assembly as the entry point; use #:ref when you want project-reference-like boundaries.
Guidelines:
#:property OutputType=Library in that referenced file.#:ref is transitive: a referenced file can contain its own #:ref and other #: directives.#:property ExperimentalFileBasedProgramEnableRefDirective=true; remove that property if the SDK accepts #:ref without it.#:sdk — SDK selectionOverride the default SDK (Microsoft.NET.Sdk):
#:include and #:exclude — Multi-file appsIn .NET SDK 10.0.300 and later, file-based apps can include additional files in the same virtual project. Check the full dotnet --version output before using these directives; a 10.0.100 or 10.0.200 SDK is still .NET 10 but does not support them. Use #:include for helper source files and supported assets, and #:exclude to remove files from an include pattern or default item set.
Guidelines:
dotnet as the entry point; put top-level statements there..cs files.Helpers.cs or Models/*.cs over broad recursive globs.#:package, #:property, #:sdk, #:project, #:ref, #:include, or #:exclude directives.#:package, #:property, #:sdk, #:include, and #:exclude entries can fail.#:include, add a shebang (#!/usr/bin/env dotnet) to the entry-point file on Unix-like systems to make the entry point clear to tools. Use LF line endings and no BOM for shebang files.Example layout:
Remove the app files when the user is done. To clear cached build artifacts:
On Unix platforms, make a .cs file directly executable:
Add a shebang as the first line of the file:
Set execute permissions:
Run directly:
Use LF line endings (not CRLF) when adding a shebang. This directive is ignored on Windows.
File-based apps enable native AOT by default. Reflection-based APIs like JsonSerializer.Serialize<T>(value) fail at runtime under AOT. Use source-generated serialization instead:
When a file-based app outgrows this workflow, convert it to a full project:
If the .NET SDK version is below 10, file-based apps are not available. Use a temporary console project instead:
Replace the generated Program.cs with the app content and run with dotnet run. Add NuGet packages with dotnet add package <name>. Remove the directory when done.
dotnet --version reports 10.0 or later (or fallback path is used)#:include, #:exclude, or transitive directives from included files, dotnet --version reports SDK 10.0.300 or laterdotnet build <file>.cs)dotnet <file>.cs produces the expected output| Pitfall | Solution |
|---|---|
.cs file is inside a directory with a .csproj | Move the app outside the project directory, or use dotnet run --file file.cs |
#:package without a version | Specify a version: #:package PackageName@1.2.3 or @* for latest |
#:property with wrong syntax | Use PropertyName=Value with no spaces around = and no quotes: #:property AllowUnsafeBlocks=true |
| Directives placed after C# code | All #: directives must appear immediately after an optional shebang line (if present) and before any using directives or other C# statements |
| Helper file is not compiled | Add #:include Helper.cs or an appropriate glob to the entry-point file |
| Shared file needs an assembly boundary | Use #:ref Shared.cs instead of #:include Shared.cs, and set #:property OutputType=Library in the referenced file if it has no entry point |
| Broad include pulls in unrelated files | Prefer narrow include patterns and use #:exclude for generated, backup, or experimental files |
| Duplicate directives in included files | Keep package, property, SDK, include, and exclude directives unique across the entry point and included C# files |
| Reflection-based JSON serialization fails | Use source-generated JSON with JsonSerializerContext (see Source-generated JSON) |
| Unexpected build behavior or version errors | File-based apps inherit global.json, Directory.Build.props, Directory.Build.targets, and nuget.config from parent directories. Move the app to an isolated directory if the inherited settings conflict |
See https://learn.microsoft.com/en-us/dotnet/core/sdk/file-based-apps for a full reference on file-based apps.
dotnet hello.csdotnet hello.cs -- arg1 arg2 "multi word arg"#:package Humanizer@2.14.1
using Humanizer;
Console.WriteLine("hello world".Titleize());#:property AllowUnsafeBlocks=true
#:property PublishAot=false
#:property NoWarn=CS0162#:property LogLevel=$([MSBuild]::ValueOrDefault('$(LOG_LEVEL)', 'Information'))#:project ../MyLibrary/MyLibrary.csproj#:property ExperimentalFileBasedProgramEnableRefDirective=true
#:ref ../Shared/Formatter.cs
Console.WriteLine(Formatter.Title("hello world"));#:sdk Microsoft.NET.Sdk.Web#!/usr/bin/env dotnet
#:include Helpers.cs
#:include Models/*.cs
#:exclude Models/Generated/*.cs
Console.WriteLine(Formatter.Title("hello world"));scratch/
hello.cs
Helpers.cs
Models/
Person.cs#!/usr/bin/env dotnet
// hello.cs
#:include Helpers.cs
#:include Models/*.cs
var person = new Person("Ada");
Console.WriteLine(Formatter.Title(person.Name));// Helpers.cs
static class Formatter
{
public static string Title(string value) => value.ToUpperInvariant();
}// Models/Person.cs
record Person(string Name);dotnet clean hello.cs