Skip to content

Commit 2f07dec

Browse files
authored
Fix open issues on the file-based apps tutorial (#50568)
* Fix duplicate snippet tags * General edit pass Also, add a link to the (in progress) SDK reference article for file-based apps. * Respond to feedback. * Remove incomplete `#:` list Remove the incomplete list of `#:` preprocessor tags from this reference. Instead link to the new SDK reference article.
1 parent 0a5c698 commit 2f07dec

File tree

3 files changed

+33
-93
lines changed

3 files changed

+33
-93
lines changed

docs/csharp/fundamentals/tutorials/file-based-programs.md

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
---
22
title: Build file-based apps
33
description: File-based apps are command line utilities that are built and execute without a project file. The build and run commands are implicit. New syntax supports project settings in source.
4-
ms.date: 08/14/2025
4+
ms.date: 12/12/2025
55
ms.topic: tutorial
66
ai-usage: ai-assisted
77
#customer intent: As a developer, I want to build utilities so that more work is automated.
88
---
99

1010
# Tutorial: Build file-based C# programs
1111

12-
*File-based apps* are programs contained within a single `*.cs` file that are built and run without a corresponding project (`*.csproj`) file. File-based apps are ideal for learning C# because they have less complexity: The entire program is stored in a single file. File-based apps are also useful for building command line utilities. On Unix platforms, file-based apps can be run using `#!` (shebang) [directives](../../language-reference/preprocessor-directives.md).
12+
*File-based apps* are programs contained within a single `*.cs` file that you build and run without a corresponding project (`*.csproj`) file. File-based apps are ideal for learning C# because they have less complexity: The entire program is stored in a single file. File-based apps are also useful for building command line utilities. On Unix platforms, you can run file-based apps by using `#!` (shebang) [directives](../../language-reference/preprocessor-directives.md).
1313
In this tutorial, you:
1414

1515
> [!div class="checklist"]
@@ -23,7 +23,7 @@ In this tutorial, you:
2323
> * Use parsed command line results.
2424
> * Test the final application.
2525
26-
You build a file-based program that writes text as ASCII art. The app is contained in a single file, uses NuGet packages that implement some of the core features.
26+
You build a file-based program that writes text as ASCII art. The app is contained in a single file, uses NuGet packages, and implements core features.
2727

2828
## Prerequisites
2929

@@ -45,31 +45,31 @@ You build a file-based program that writes text as ASCII art. The app is contain
4545
dotnet run AsciiArt.cs
4646
```
4747

48-
The first time you run this program, the `dotnet` host builds the executable from your source file, stores build artifacts in a temporary folder, then runs the created executable. You can verify this experience by typing `dotnet run AsciiArt.cs` again. This time, the `dotnet` host determines that the executable is current, and runs the executable without building it again. You don't see any build output.
48+
The first time you run this program, the `dotnet` host builds the executable from your source file, stores build artifacts in a temporary folder, and then runs the created executable. You can verify this experience by typing `dotnet run AsciiArt.cs` again. This time, the `dotnet` host determines that the executable is current and runs the executable without building it again. You don't see any build output.
4949

50-
The preceding steps demonstrate that file-based apps aren't script files. They're C# source files that are built using a generated project file in a temporary folder. One of the lines of output displayed when you built the program should look something like this (on Windows):
50+
The preceding steps demonstrate that file-based apps aren't script files. They're C# source files that the `dotnet` host builds by using a generated project file in a temporary folder. One of the lines of output displayed when you build the program should look something like this (on Windows):
5151

5252
```dotnetcli
5353
AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll
5454
```
5555

56-
On unix platforms, the output folder is something similar to:
56+
On Unix platforms, the output folder is something similar to:
5757

5858
```dotnetcli
5959
AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll
6060
```
6161

6262
That output tells you where the temporary files and build outputs are placed. Throughout this tutorial, anytime you edit the source file, the `dotnet` host updates the executable before it runs.
6363

64-
File-based apps are regular C# programs. The only limitation is that they must be written in one source file. You can use top-level statements or a classic `Main` method as an entry point. You can declare any types: classes, interfaces, and structs. You can structure the algorithms in a file-based program the same as you would in any C# program. You can even declare multiple namespaces to organize your code. If you find a file-based program is growing too large for a single file, you can convert it to a project based program and split the source into multiple files. File-based apps are a great prototyping tool. You can start experimenting with minimal overhead to prove concepts and build algorithms.
64+
File-based apps are regular C# programs. The only limitation is that you must write them in one source file. You can use top-level statements or a classic `Main` method as an entry point. You can declare any types: classes, interfaces, and structs. You can structure the algorithms in a file-based program the same as you would in any C# program. You can even declare multiple namespaces to organize your code. If you find a file-based program is growing too large for a single file, you can convert it to a project-based program and split the source into multiple files. File-based apps are a great prototyping tool. You can start experimenting with minimal overhead to prove concepts and build algorithms.
6565

6666
## Unix shebang (`#!`) support
6767

6868
> [!NOTE]
6969
>
70-
> Support for `#!` directives applies on unix platforms only. There isn't a similar directive for Windows to directly execute a C# program. On Windows, you must use `dotnet run` on the command line.
70+
> Support for `#!` directives applies on Unix platforms only. There's no similar directive for Windows to directly execute a C# program. On Windows, you must use `dotnet run` on the command line.
7171
72-
On unix, you can run file-based apps directly, typing the source file name on the command line instead of `dotnet run`. You need to make two changes:
72+
On Unix, you can run file-based apps directly. Instead of using `dotnet run`, you type the source file name on the command line. You need to make two changes:
7373

7474
1. Set *execute* permissions on the source file:
7575

@@ -83,7 +83,7 @@ On unix, you can run file-based apps directly, typing the source file name on th
8383
#!/usr/local/share/dotnet/dotnet run
8484
```
8585

86-
The location of `dotnet` can be different on different unix installations. Use the command `which dotnet` to locate the `dotnet` host in your environment.
86+
The location of `dotnet` can be different on different Unix installations. Use the command `which dotnet` to locate the `dotnet` host in your environment.
8787

8888
Alternatively, you can use `#!/usr/bin/env dotnet` to resolve the dotnet path from the PATH environment variable automatically:
8989

@@ -123,13 +123,13 @@ Now, write all arguments on the command line to the output.
123123

124124
This version demonstrates these new concepts:
125125

126-
- The command line arguments are passed to the program using the predefined variable `args`. The `args` variable is an array of strings: `string[]`. If the length of `args` is 0, that means no arguments were provided. Otherwise, each word on the argument list is stored in the corresponding entry in the array.
126+
- The predefined variable `args` passes the command line arguments to the program. The `args` variable is an array of strings: `string[]`. If the length of `args` is 0, no arguments were provided. Otherwise, each word on the argument list is stored in the corresponding entry in the array.
127127
- The [`string.Join`](xref:System.String.Join*) method joins multiple strings into a single string, with the specified separator. In this case, the separator is a single space.
128128
- <xref:System.Console.WriteLine*?displayProperty=nameWithType> writes the string to the standard output console, followed by a new line.
129129

130130
## Handle standard input
131131

132-
That handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments.
132+
The preceding code handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments.
133133

134134
1. Add the following `else` clause to the `if` statement you added in the preceding code:
135135

@@ -153,25 +153,25 @@ That handles command line arguments correctly. Now, add the code to handle readi
153153

154154
1. Run the program again.
155155

156-
With bash:
156+
By using bash:
157157

158158
```bash
159159
cat input.txt | dotnet run AsciiArt.cs
160160
```
161161

162-
Or, with PowerShell:
162+
Or, by using PowerShell:
163163

164164
```powershell
165165
Get-Content input.txt | dotnet run AsciiArt.cs
166166
```
167167

168168
Now your program can accept either command line arguments or standard input.
169169

170-
## Write ASCII Art output
170+
## Write ASCII art output
171171

172-
Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuget.org/packages/Colorful.Console). To add a package to a file-based program, you use the `#:package` directive.
172+
Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuget.org/packages/Colorful.Console). To add a package to a file-based program, use the `#:package` directive.
173173

174-
1. Add the following directive after the `#!` directive in your AsciiArt.cs file:
174+
1. Add the following directive after the `#!` directive in your `AsciiArt.cs` file:
175175

176176
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ColorfulPackage":::
177177

@@ -182,11 +182,11 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge
182182

183183
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii":::
184184

185-
1. Run the program, and you see ASCII art output instead of echoed text.
185+
1. Run the program. You see ASCII art output instead of echoed text.
186186

187187
## Process command options
188188

189-
Next, let's add command line parsing. The current version writes each word as a different line of output. The command line arguments you added support two features:
189+
Next, add command line parsing. The current version writes each word as a different line of output. The command line arguments you add support two features:
190190

191191
1. Quote multiple words that should be written on one line:
192192

@@ -200,9 +200,9 @@ Next, let's add command line parsing. The current version writes each word as a
200200
AsciiArt.cs --delay 1000
201201
```
202202

203-
Users should be able to use both arguments together.
203+
Users can use both arguments together.
204204

205-
Most command line applications need to parse command line arguments to handle options, commands, and user input effectively. The [`System.CommandLine` library](../../../standard/commandline/index.md) provides comprehensive capabilities to handle commands, subcommands, options, and arguments, allowing you to concentrate on what your application does rather than the mechanics of parsing command line input.
205+
Most command line applications need to parse command line arguments to handle options, commands, and user input effectively. The [`System.CommandLine` library](../../../standard/commandline/index.md) provides comprehensive capabilities to handle commands, subcommands, options, and arguments. You can concentrate on what your application does rather than the mechanics of parsing command line input.
206206

207207
The `System.CommandLine` library offers several key benefits:
208208

@@ -238,7 +238,7 @@ The `System.CommandLine` library offers several key benefits:
238238

239239
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ParseAndValidate":::
240240

241-
The preceding code validates all command line arguments. If the validation fails, errors are written to the console, and the app exits.
241+
The preceding code validates all command line arguments. If the validation fails, the app writes errors to the console and exits.
242242

243243
## Use parsed command line results
244244

@@ -254,9 +254,9 @@ Now, finish the app to use the parsed options and write the output. First, defin
254254

255255
1. Create a local function to write the ASCII art with the specified delay. This function writes each message in the record with the specified delay between each message:
256256

257-
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii":::
257+
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAsciiArt":::
258258

259-
1. Replace the `if` clause you wrote earlier with the following code that processes the command line arguments and write the output:
259+
1. Replace the `if` clause you wrote earlier with the following code that processes the command line arguments and writes the output:
260260

261261
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="InvokeCommand":::
262262

@@ -268,10 +268,11 @@ Test the application by running several different commands. If you have trouble,
268268

269269
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt":::
270270

271-
In this tutorial, you learned to build a file-based program, where you build the program in a single C# file. These programs don't use a project file, and can use the `#!` directive on unix systems. Learners can create these programs after trying our [online tutorials](../../tour-of-csharp/tutorials/hello-world.md) and before building larger project-based apps. File-based apps are also a great platform for command line utilities.
271+
In this tutorial, you learned to build a file-based program, where you build the program in a single C# file. These programs don't use a project file, and can use the `#!` directive on Unix systems. Learners can create these programs after trying our [online tutorials](../../tour-of-csharp/tutorials/hello-world.md) and before building larger project-based apps. File-based apps are also a great platform for command line utilities.
272272

273273
## Related content
274274

275+
- [File-based apps reference](../../../core/sdk/file-based-apps.md)
275276
- [Top level statement](../program-structure/top-level-statements.md)
276277
- [Preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-apps)
277278
- [What's new in C# 14](../../whats-new/csharp-14.md)

docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/local/share/dotnet/dotnet run
1+
#!/usr/bin/env dotnet run
22

33
// <ColorfulPackage>
44
#:package Colorful.Console@1.2.15
@@ -71,7 +71,8 @@ async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
7171
}
7272
// </ProcessParsedArgs>
7373

74-
// <WriteAscii>
74+
75+
// <WriteAsciiArt>
7576
async Task WriteAsciiArt(AsciiMessageOptions options)
7677
{
7778
foreach (string message in options.Messages)
@@ -80,7 +81,7 @@ async Task WriteAsciiArt(AsciiMessageOptions options)
8081
await Task.Delay(options.Delay);
8182
}
8283
}
83-
// </WriteAscii>
84+
// </WriteAsciiArt>
8485

8586
// <Record>
8687
public record AsciiMessageOptions(string[] Messages, int Delay);

docs/csharp/language-reference/preprocessor-directives.md

Lines changed: 3 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,7 @@ f1_keywords:
2121
- "#pragma checksum"
2222
- "defaultline_CSharpKeyword"
2323
- "#!"
24-
- "#:sdk"
25-
- "#:property"
26-
- "#:package"
24+
- "#:"
2725
helpviewer_keywords:
2826
- "preprocessor directives [C#]"
2927
- "keywords [C#], preprocessor directives"
@@ -62,69 +60,9 @@ Console.WriteLine("Hello");
6260

6361
The preceding code snippet informs a Unix shell to execute the file using `dotnet run`. The `/usr/bin/env` command locates the `dotnet` executable in your PATH, making this approach portable across different Unix and macOS distributions. The `#!` line must be the first line in the file, and the following tokens are the program to run. You need to enable the *execute* (`x`) permission on the C# file for that feature.
6462

65-
The `#:` directives that are used in file-based apps include:
66-
67-
- `#:sdk`:
68-
69-
The first instance specifies the value for the `<Project Sdk="value" />` node. Subsequent instances specify the `<Sdk Name="value" Version="version" />` node. The version can be omitted (i.e. if specified in global.json or included in .NET SDK). For example:
70-
71-
```csharp
72-
#:sdk Microsoft.NET.Sdk.Web
73-
#:sdk Aspire.AppHost.Sdk@9.4.1
74-
```
75-
76-
The two preceding preprocessors is translated into:
77-
78-
```xml
79-
<Project Sdk="Microsoft.NET.Sdk.Web" />
80-
<Sdk Name="Aspire.AppHost.Sdk" Version="9.4.1" />
81-
```
82-
83-
- `#:property`:
84-
85-
Instances of `#:property` are translated into property elements in a `<PropertyGroup>`. A token of the form `Name=value` must follow the `property` token. The following example directives are valid `property` tokens:
86-
87-
```csharp
88-
#:property TargetFramework=net11.0
89-
#:property LangVersion=preview
90-
```
91-
92-
The preceding two properties are translated into:
93-
94-
```xml
95-
<TargetFramework>net11.0</TargetFramework>
96-
<LangVersion>preview</LangVersion>
97-
```
98-
99-
- `#:package`:
100-
101-
Instances of `#:package` are translated into `PackageReference` elements to include NuGet packages with the specified version to your file. For example:
102-
103-
```csharp
104-
#:package System.CommandLine@2.0.0-*
105-
```
106-
107-
The preceding preprocessor token is translated into:
108-
109-
```xml
110-
<PackageReference Include="System.CommandLine" Version="2.0.0-*">
111-
```
112-
113-
- `#:project`:
114-
115-
Instances of `#:project` are translated into `ProjectReference` elements to include the project with the specified path to the project. For example:
116-
117-
```csharp
118-
#:project ../Path/To.Example
119-
```
120-
121-
The preceding preprocessor token is translated into:
122-
123-
```xml
124-
<ProjectReference Include="../Path/To.Example/To.Example.csproj" />
125-
```
63+
The `#:` directives that are used in file-based apps are described in the [file based apps reference](../../core/sdk/file-based-apps.md).
12664

127-
Tools can add new tokens following the `#:` convention.
65+
Other tools can add new tokens following the `#:` convention.
12866

12967
## Nullable context
13068

0 commit comments

Comments
 (0)