Agent Skills: C# File-Based Apps (.NET 10+)

Write and run C# programs without a .csproj or solution file using .NET 10+ file-based apps, including multi-file apps with `#:include` (.NET 11+). Use when the user wants to execute C# code quickly — scripts, utilities, prototyping, quick experiments, data processing, teaching examples, or tool authoring. Triggers on phrases like "run this C# file", "C# script", "no project file needed", "single file C# app", "dotnet run file.cs", "quick C# utility", "split script across files", "#:include directive", or any request to write and immediately execute a small self-contained C# program.

UncategorizedID: arisng/github-copilot-fc/csharp-file-based-apps

Install this agent skill to your local

pnpm dlx add-skill https://github.com/arisng/github-copilot-fc/tree/HEAD/skills/csharp-file-based-apps

Skill Files

Browse the full folder contents for csharp-file-based-apps.

Download Skill

Loading file tree…

skills/csharp-file-based-apps/SKILL.md

Skill Metadata

Name
csharp-file-based-apps
Description
Write and run C# programs without a .csproj or solution file using .NET 10+ file-based apps, including multi-file apps with `#:include` (.NET 11+). Use when the user wants to execute C# code quickly — scripts, utilities, prototyping, quick experiments, data processing, teaching examples, or tool authoring. Triggers on phrases like "run this C# file", "C# script", "no project file needed", "single file C# app", "dotnet run file.cs", "quick C# utility", "split script across files", "#:include directive", or any request to write and immediately execute a small self-contained C# program.

C# File-Based Apps (.NET 10+)

Run a single .cs file directly — no .csproj required. The SDK generates a virtual project automatically.

Requirements

  • Single-file apps: .NET 10 SDK or later.
  • Multi-file (#:include): .NET 11 SDK or later.

Verify with dotnet --version. If below 10, use the fallback.

Write the Script

Create script.cs outside any existing .csproj directory:

// script.cs — uses top-level statements
using System.Linq;

var numbers = Enumerable.Range(1, 10);
Console.WriteLine($"Sum: {numbers.Sum()}");

Rules:

  • using directives first
  • Top-level executable code next
  • Type declarations (classes, records, enums) last — after all top-level statements

Run, Build, Publish

| Goal | Command | |------|---------| | Run | dotnet script.cs or dotnet run script.cs | | Pass arguments | dotnet script.cs -- arg1 arg2 | | Pipe code from stdin | echo 'Console.WriteLine("hi");' \| dotnet run - | | Build only | dotnet build script.cs | | Publish (native AOT) | dotnet publish script.cs | | Package as .NET tool | dotnet pack script.cs | | Clean build cache | dotnet clean script.cs | | Convert to full project | dotnet project convert script.cs | | Restore packages | dotnet restore script.cs |

Builds are cached by source hash; subsequent runs are fast. To avoid cache contention when running multiple parallel instances, build first: dotnet build script.cs, then dotnet run script.cs --no-build.

Directives

Place at the top of the file, before any using or code:

#:package Humanizer@2.14.1           // NuGet reference — always specify a version
#:package Serilog@*                  // @* = latest
#:project ../SharedLib/Shared.csproj // Reference a local project
#:sdk Microsoft.NET.Sdk.Web          // Alternative SDK (default: Microsoft.NET.Sdk)
#:include helpers.cs                 // Include another .cs file (.NET 11+)
#:include models/customer.cs         // Include from subfolder (.NET 11+)
#:property PublishAot=false          // Disable native AOT
#:property TargetFramework=net10.0   // Explicit TFM
#:property OutputPath=./output       // Custom build output path

Conditional property using MSBuild expression:

#:property LogLevel=$([MSBuild]::ValueOrDefault('$(LOG_LEVEL)', 'Information'))

Multi-File Apps (.NET 11+)

Use #:include to split a growing script across files while keeping the file-based workflow:

// models.cs
public record Customer(int Id, string Name, string Email);
// helpers.cs
public static class Helpers
{
    public static string Format(Customer c) =>
        $"[{c.Id}] {c.Name} <{c.Email}>";
}
// main.cs  ← entry point
#:include models.cs
#:include helpers.cs

var customer = new Customer(1, "John Doe", "john@example.com");
Console.WriteLine(Helpers.Format(customer));

Run from the entry point: dotnet run main.cs

Files can live in subfolders: #:include services/email-service.cs. Roslyn provides IntelliSense for #:include in the entry-point file.

See references/examples.md for complete multi-file examples (HTTP health checker, Minimal API with EF Core + SQLite).

Native AOT Gotcha

Native AOT is on by default. Reflection-based serialization (JsonSerializer without a context, Newtonsoft.Json) fails at runtime under AOT. Use source-generated JSON:

using System.Text.Json;
using System.Text.Json.Serialization;

var person = new Person("Alice", 30);
Console.WriteLine(JsonSerializer.Serialize(person, AppCtx.Default.Person));

record Person(string Name, int Age);

[JsonSerializable(typeof(Person))]
partial class AppCtx : JsonSerializerContext;

To use reflection-based APIs, disable AOT: #:property PublishAot=false.

Common Pitfalls

| Problem | Fix | |---------|-----| | Script inside a .csproj directory | Move outside the project tree; or use dotnet run --file script.cs | | #:package without version | Use @version or @* for latest | | Reflection JSON fails at runtime | Source-generated JSON, or #:property PublishAot=false | | Stale / unexpected build behavior | dotnet clean script.cs && dotnet build script.cs | | Inherits unwanted Directory.Build.props | Move to isolated directory or add a local empty Directory.Build.props | | Running multiple instances in parallel | Build first, then all instances use --no-build | | #:include not found | Requires .NET 11+; verify with dotnet --version | | Included file has top-level statements | Only the entry-point file may have top-level statements; included files declare types only |

Unix Shebang (Unix/macOS only)

#!/usr/bin/env dotnet
Console.WriteLine("Direct execution!");
chmod +x script.cs
./script.cs

Use LF line endings (not CRLF). The shebang line is ignored on Windows.

Launch Profiles

Create script.run.json alongside script.cs:

{
  "profiles": {
    "http": {
      "commandName": "Project",
      "applicationUrl": "http://localhost:5000",
      "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }
    }
  }
}

Run with: dotnet run script.cs --launch-profile http

User Secrets

dotnet user-secrets set "ApiKey" "value" --file script.cs
dotnet user-secrets list --file script.cs

Fallback for .NET 9 and Earlier {#fallback}

File-based apps require .NET 10+. Use a temporary console project:

mkdir -p /tmp/csharp-script && cd /tmp/csharp-script
dotnet new console -o . --force
# Replace Program.cs content, then:
dotnet run
# Add packages with: dotnet add package <Name>

When to Convert to a Full Project

Convert with dotnet project convert main.cs when:

  • You need multiple projects (Web API + class library + test project)
  • Custom MSBuild targets or conditional compilation are required
  • Distributing as a NuGet package or self-contained executable
  • CI/CD pipelines expect a csproj
  • The team grows and csproj familiarity matters

Start file-based, grow as needed, convert only when you exceed the approach.

More Examples

See references/examples.md for: data processing with NuGet packages, ASP.NET Core minimal API, Aspire AppHost, Spectre.Console CLI tool, and multi-file examples (HTTP health checker, Minimal API with EF Core + SQLite).