mirror of
https://github.com/dotnet/skills.git
synced 2026-09-20 09:49:54 +08:00
Update file-based app skill to mention features from 10.0.300 (#602)
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
+2
-2
@@ -10,8 +10,8 @@
|
||||
/tests/dotnet-msbuild/ @ViktorHofer @JanKrivanek @dotnet/skills-msbuild-reviewers
|
||||
|
||||
# dotnet (common everyday C#/.NET)
|
||||
/plugins/dotnet/skills/csharp-scripts/ @dotnet/roslyn @dotnet/skills-csharp-language-reviewers
|
||||
/tests/dotnet/csharp-scripts/ @dotnet/roslyn @dotnet/skills-csharp-language-reviewers
|
||||
/plugins/dotnet/skills/csharp-scripts/ @dotnet/run-file @dotnet/skills-csharp-language-reviewers
|
||||
/tests/dotnet/csharp-scripts/ @dotnet/run-file @dotnet/skills-csharp-language-reviewers
|
||||
|
||||
/plugins/dotnet/lsp.json @dotnet/roslyn-ide @dotnet/skills-csharp-language-reviewers
|
||||
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
---
|
||||
name: csharp-scripts
|
||||
description: Run single-file C# programs as scripts (file-based apps) for quick experimentation, prototyping, and concept testing. Use when the user wants to write and execute a small C# program without creating a full project.
|
||||
description: "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."
|
||||
license: MIT
|
||||
---
|
||||
|
||||
# C# Scripts
|
||||
# File-Based C# Apps
|
||||
|
||||
## When to Use
|
||||
|
||||
- Testing a C# concept, API, or language feature with a quick one-file program
|
||||
- Testing a C# concept, API, or language feature with a quick file-based app
|
||||
- Prototyping logic before integrating it into a larger project
|
||||
- Building a small utility from one entry-point file and a few helper `.cs` files
|
||||
|
||||
## When Not to Use
|
||||
|
||||
- The user needs a full project with multiple files or project references
|
||||
- The user asks for a language-agnostic quick script, throwaway computation, or shell/Python/PowerShell-style automation
|
||||
- The user needs a full project, solution integration, or project references in an existing app
|
||||
- The user is working inside an existing .NET solution and wants to add code there
|
||||
- The program is too large or complex for a single file
|
||||
- The app is large enough that project structure, build customization, tests, or publish configuration should live in a `.csproj`
|
||||
|
||||
## Inputs
|
||||
|
||||
| Input | Required | Description |
|
||||
|-------|----------|-------------|
|
||||
| C# code or intent | Yes | The code to run, or a description of what the script should do |
|
||||
| C# code or intent | Yes | The code to run, or a description of what the file-based app should do |
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 1: Check the .NET SDK version
|
||||
|
||||
Run `dotnet --version` to verify the SDK is installed and note the major version number. File-based apps require .NET 10 or later. If the version is below 10, follow the [fallback for older SDKs](#fallback-for-net-9-and-earlier) instead.
|
||||
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](#fallback-for-net-9-and-earlier) instead.
|
||||
|
||||
### Step 2: Write the script file
|
||||
### Step 2: Write the app file
|
||||
|
||||
Create a single `.cs` file using top-level statements. Place it outside any existing project directory to avoid conflicts with `.csproj` files.
|
||||
Create an entry-point `.cs` file using top-level statements. Place it outside any existing project directory to avoid conflicts with `.csproj` files.
|
||||
|
||||
```csharp
|
||||
#!/usr/bin/env dotnet
|
||||
// hello.cs
|
||||
Console.WriteLine("Hello from a C# script!");
|
||||
Console.WriteLine("Hello from a file-based app!");
|
||||
|
||||
var numbers = new[] { 1, 2, 3, 4, 5 };
|
||||
Console.WriteLine($"Sum: {numbers.Sum()}");
|
||||
@@ -47,7 +50,7 @@ Guidelines:
|
||||
- Place `using` directives at the top of the file (after the `#!` line and any `#:` directives if present)
|
||||
- Place type declarations (classes, records, enums) after all top-level statements
|
||||
|
||||
### Step 3: Run the script
|
||||
### Step 3: Run the app
|
||||
|
||||
```bash
|
||||
dotnet hello.cs
|
||||
@@ -65,7 +68,7 @@ Place directives at the top of the file (immediately after an optional shebang l
|
||||
|
||||
#### `#:package` — NuGet package references
|
||||
|
||||
Always specify a version:
|
||||
Specify a version unless the app intentionally uses central package management. Use `@*` when the latest available package is acceptable (or `@*-*` for pre-release):
|
||||
|
||||
```csharp
|
||||
#:package Humanizer@2.14.1
|
||||
@@ -109,6 +112,26 @@ Reference another project by relative path:
|
||||
#:project ../MyLibrary/MyLibrary.csproj
|
||||
```
|
||||
|
||||
#### `#:ref` — File-based app references
|
||||
|
||||
Reference 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.
|
||||
|
||||
```csharp
|
||||
#:property ExperimentalFileBasedProgramEnableRefDirective=true
|
||||
#:ref ../Shared/Formatter.cs
|
||||
|
||||
Console.WriteLine(Formatter.Title("hello world"));
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
- The referenced file is compiled as its own virtual project and added as a project reference.
|
||||
- If the referenced file is a library without top-level statements, put `#:property OutputType=Library` in that referenced file.
|
||||
- Members that must be consumed by the referencing app should be public; internal members are not visible across the assembly boundary.
|
||||
- `#:ref` is transitive: a referenced file can contain its own `#:ref` and other `#:` directives.
|
||||
- Relative paths are resolved relative to the file containing the directive.
|
||||
- Some SDK builds require `#:property ExperimentalFileBasedProgramEnableRefDirective=true`; remove that property if the SDK accepts `#:ref` without it.
|
||||
|
||||
#### `#:sdk` — SDK selection
|
||||
|
||||
Override the default SDK (`Microsoft.NET.Sdk`):
|
||||
@@ -117,9 +140,65 @@ Override the default SDK (`Microsoft.NET.Sdk`):
|
||||
#:sdk Microsoft.NET.Sdk.Web
|
||||
```
|
||||
|
||||
#### `#:include` and `#:exclude` — Multi-file apps
|
||||
|
||||
In .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.
|
||||
|
||||
```csharp
|
||||
#!/usr/bin/env dotnet
|
||||
#:include Helpers.cs
|
||||
#:include Models/*.cs
|
||||
#:exclude Models/Generated/*.cs
|
||||
|
||||
Console.WriteLine(Formatter.Title("hello world"));
|
||||
```
|
||||
|
||||
Guidelines:
|
||||
|
||||
- Treat the file passed to `dotnet` as the entry point; put top-level statements there.
|
||||
- Put declarations such as classes, records, and enums in included `.cs` files.
|
||||
- Prefer explicit globs such as `Helpers.cs` or `Models/*.cs` over broad recursive globs.
|
||||
- Paths are resolved relative to the file containing the directive.
|
||||
- Include directives from non-entry-point C# files are processed too, so a helper file can declare its own `#:package`, `#:property`, `#:sdk`, `#:project`, `#:ref`, `#:include`, or `#:exclude` directives.
|
||||
- Avoid duplicate directives across included files unless the directive kind explicitly supports duplicates; duplicate `#:package`, `#:property`, `#:sdk`, `#:include`, and `#:exclude` entries can fail.
|
||||
- When an app uses `#: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:
|
||||
|
||||
```text
|
||||
scratch/
|
||||
hello.cs
|
||||
Helpers.cs
|
||||
Models/
|
||||
Person.cs
|
||||
```
|
||||
|
||||
```csharp
|
||||
#!/usr/bin/env dotnet
|
||||
// hello.cs
|
||||
#:include Helpers.cs
|
||||
#:include Models/*.cs
|
||||
|
||||
var person = new Person("Ada");
|
||||
Console.WriteLine(Formatter.Title(person.Name));
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Helpers.cs
|
||||
static class Formatter
|
||||
{
|
||||
public static string Title(string value) => value.ToUpperInvariant();
|
||||
}
|
||||
```
|
||||
|
||||
```csharp
|
||||
// Models/Person.cs
|
||||
record Person(string Name);
|
||||
```
|
||||
|
||||
### Step 5: Clean up
|
||||
|
||||
Remove the script file when the user is done. To clear cached build artifacts:
|
||||
Remove the app files when the user is done. To clear cached build artifacts:
|
||||
|
||||
```bash
|
||||
dotnet clean hello.cs
|
||||
@@ -173,7 +252,7 @@ partial class AppJsonContext : JsonSerializerContext;
|
||||
|
||||
## Converting to a project
|
||||
|
||||
When a script outgrows a single file, convert it to a full project:
|
||||
When a file-based app outgrows this workflow, convert it to a full project:
|
||||
|
||||
```bash
|
||||
dotnet project convert hello.cs
|
||||
@@ -184,29 +263,35 @@ dotnet project convert hello.cs
|
||||
If the .NET SDK version is below 10, file-based apps are not available. Use a temporary console project instead:
|
||||
|
||||
```bash
|
||||
mkdir -p /tmp/csharp-script && cd /tmp/csharp-script
|
||||
mkdir -p /tmp/csharp-file-based-app && cd /tmp/csharp-file-based-app
|
||||
dotnet new console -o . --force
|
||||
```
|
||||
|
||||
Replace the generated `Program.cs` with the script content and run with `dotnet run`. Add NuGet packages with `dotnet add package <name>`. Remove the directory when done.
|
||||
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.
|
||||
|
||||
## Validation
|
||||
|
||||
- [ ] `dotnet --version` reports 10.0 or later (or fallback path is used)
|
||||
- [ ] The script compiles without errors (can be checked explicitly with `dotnet build <file>.cs`)
|
||||
- [ ] If the app uses `#:include`, `#:exclude`, or transitive directives from included files, `dotnet --version` reports SDK 10.0.300 or later
|
||||
- [ ] The app compiles without errors (can be checked explicitly with `dotnet build <file>.cs`)
|
||||
- [ ] `dotnet <file>.cs` produces the expected output
|
||||
- [ ] Script file and cached artifacts are cleaned up after the session
|
||||
- [ ] Multi-file apps include every required helper file and exclude unintended matches
|
||||
- [ ] App files and cached artifacts are cleaned up after the session
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
| Pitfall | Solution |
|
||||
|---------|----------|
|
||||
| `.cs` file is inside a directory with a `.csproj` | Move the script outside the project directory, or use `dotnet run --file file.cs` |
|
||||
| `.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](#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 script to an isolated directory if the inherited settings conflict |
|
||||
| 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 |
|
||||
|
||||
## More info
|
||||
|
||||
|
||||
@@ -1,5 +1,29 @@
|
||||
scenarios:
|
||||
- name: "Test a C# language feature with a script"
|
||||
- name: "Avoid activating for language-agnostic calendar script"
|
||||
prompt: >
|
||||
Write a quick script to experiment with finding the earliest Friday the
|
||||
13th starting at 0001-01-01 in the proleptic Gregorian calendar. Also
|
||||
count how many Friday the 13ths exist in a full 400-year cycle. Execute it and show results --
|
||||
no need for a full project, just a small script for quick prototyping.
|
||||
expect_activation: false
|
||||
assertions:
|
||||
- type: "exit_success"
|
||||
- type: "output_contains"
|
||||
value: "Friday"
|
||||
- type: "output_matches"
|
||||
pattern: "(April|4/13|0001-04-13)"
|
||||
- type: "output_contains"
|
||||
value: "688"
|
||||
expect_tools: ["bash"]
|
||||
max_turns: 10
|
||||
rubric:
|
||||
- "The agent correctly finds April 13, 0001 as the earliest Friday the 13th"
|
||||
- "The agent correctly counts 688 Friday the 13ths in 400 years"
|
||||
- "The agent verified by running code"
|
||||
- "The csharp-scripts skill does not activate because the prompt does not ask for C# or .NET"
|
||||
timeout: 120
|
||||
|
||||
- name: "Test a C# language feature with a file-based app"
|
||||
prompt: "Does C# support creating a stackalloc'd Span of native-sized integers? Can you test it and see?"
|
||||
assertions:
|
||||
- type: "exit_success"
|
||||
@@ -14,3 +38,62 @@ scenarios:
|
||||
- "Runs the test using 'dotnet <file>.cs' (file-based app) rather than creating a full project with 'dotnet new console'"
|
||||
- "The code compiles and runs, demonstrating that stackalloc with Span<nint> works"
|
||||
timeout: 120
|
||||
|
||||
- name: "Compose a file-based app from helper files"
|
||||
prompt: |
|
||||
Create a tiny file-based C# app in the current directory using these exact files:
|
||||
hello.cs, Helpers.cs, Models/Person.cs, and Scratch.cs.
|
||||
|
||||
Requirements:
|
||||
- hello.cs is the entry point.
|
||||
- hello.cs uses #:include *.cs to include Helpers.cs and #:include Models/*.cs to include Models/Person.cs.
|
||||
- hello.cs uses #:exclude Scratch.cs so Scratch.cs is not compiled.
|
||||
- Scratch.cs should contain invalid C# with the identifier ThisShouldNotCompile, so the app would fail if Scratch.cs were included.
|
||||
- Helpers.cs should contain a helper that transforms a person's name.
|
||||
- Models/Person.cs should contain a Person model.
|
||||
- Run the app with dotnet hello.cs and make it print exactly: ADA LOVELACE
|
||||
assertions:
|
||||
- type: "exit_success"
|
||||
- type: "file_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:include"
|
||||
- type: "file_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:exclude"
|
||||
- type: "file_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:include *.cs"
|
||||
- type: "file_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:include Models/*.cs"
|
||||
- type: "file_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:exclude Scratch.cs"
|
||||
- type: "file_contains"
|
||||
path: "Helpers.cs"
|
||||
value: "Formatter"
|
||||
- type: "file_contains"
|
||||
path: "Models/Person.cs"
|
||||
value: "Person"
|
||||
- type: "file_contains"
|
||||
path: "Scratch.cs"
|
||||
value: "ThisShouldNotCompile"
|
||||
- type: "file_not_contains"
|
||||
path: "hello.cs"
|
||||
value: "#:include Scratch.cs"
|
||||
- type: "output_not_matches"
|
||||
pattern: "dotnet new console"
|
||||
- type: "run_command_and_assert"
|
||||
command_to_run: "dotnet"
|
||||
command_arguments: "hello.cs"
|
||||
expected_exit_code: 0
|
||||
expected_std_output_contains: "ADA LOVELACE"
|
||||
command_timeout: 120
|
||||
expect_tools: ["bash"]
|
||||
rubric:
|
||||
- "Uses a file-based app with an entry-point .cs file rather than creating a project"
|
||||
- "Creates hello.cs, Helpers.cs, Models/Person.cs, and Scratch.cs with the requested responsibilities"
|
||||
- "Uses #:include to compile helper/model .cs files with the entry point"
|
||||
- "Uses #:exclude to keep Scratch.cs out of the build even though *.cs is included"
|
||||
- "Runs the app with dotnet <file>.cs and reports the successful output"
|
||||
timeout: 120
|
||||
|
||||
Reference in New Issue
Block a user