Skip to content

Commit d5e450a

Browse files
committed
major edit pass
1 parent 975c9ae commit d5e450a

File tree

1 file changed

+35
-31
lines changed

1 file changed

+35
-31
lines changed

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

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Build file-based programs
33
description: File-based programs 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.
44
ms.date: 08/08/2025
55
ms.topic: tutorial
6+
ai-usage: ai-assisted
67
#customer intent: As a developer, I want build utilities so that more work is automated.
78
---
89

@@ -11,18 +12,18 @@ ms.topic: tutorial
1112
> [!IMPORTANT]
1213
>
1314
> File-based programs are a feature of .NET 10, which is in preview.
14-
> Some information relates to prerelease product that may be modified before it's released. Microsoft makes no warranties, express or implied, with respect to the information provided here.
15+
> Some information relates to prerelease product that might be modified before release. Microsoft makes no warranties, express or implied, with respect to the information provided here.
1516
1617
*File-based programs* are programs contained within a single `*.cs` file that are built and run without a corresponding project (`*.csproj`) file. File-based programs are ideal for learning C# because they have less complexity: The entire program is stored in a single file. File-based programs are also useful for building command line utilities. On Unix platforms, file-based programs can be executed using `#!` (shebang) directives.
1718

1819
In this tutorial, you:
1920

2021
> [!div class="checklist"]
2122
>
22-
> * Create a file-based program
23-
> * Run the program using the .NET CLI and `#!` directives
24-
> * Add features and NuGet packages to the program
25-
> * Test the final application
23+
> * Create a file-based program.
24+
> * Run the program using the .NET CLI and `#!` directives.
25+
> * Add features and NuGet packages to the program.
26+
> * Parse and process command line arguments and standard input.
2627
2728
## Prerequisites
2829

@@ -32,21 +33,21 @@ In this tutorial, you:
3233

3334
## Create a file-based program
3435

35-
Open Visual Studio code and create a new file named `AsciiArt.cs`. Enter the following text:
36+
Open Visual Studio Code and create a new file named `AsciiArt.cs`. Enter the following text:
3637

3738
```csharp
3839
Console.WriteLine("Hello, world!");
3940
```
4041

41-
Save the file. Then, open the integrated terminal in Visual Studio code and type:
42+
Save the file. Then, open the integrated terminal in Visual Studio Code and type:
4243

4344
```dotnetcli
4445
dotnet run AsciiArt.cs
4546
```
4647

47-
The first time you run this program, the `dotnet` host builds the executable from your source file, stores that executable in a temporary folder, then runs the executable. You can verify this by typing `dotnet run AsciiArt.cs` again. This time, the `dotnet` host determines that the executable is current, and just runs the executable without building it again. You won't see any build output. The executable runs without the delay for a build.
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. The executable runs without the extra build step.
4849

49-
The preceding steps demonstrates that file based programs aren't script files. They are C# source files that are built using a default project file in a temporary folder. One of the lines of output when you built the program should look something like this (on Windows):
50+
The preceding steps demonstrate that file based programs 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 when you built the program should look something like this (on Windows):
5051

5152
```dotnetcli
5253
AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll
@@ -58,11 +59,11 @@ On unix platforms, the output folder is something similar to:
5859
AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll
5960
```
6061

61-
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.
62+
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.
6263

63-
File based programs 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.
64+
File based programs 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 programs are a great prototyping tool. You can start experimenting with minimal overhead to prove concepts and build algorithms.
6465

65-
In this tutorial, you'll build a file-based program that writes text as ASCII art. You'll learn how to include packages in file-based programs, process command input, and read arguments either from the command line or standard input.
66+
In this tutorial, you build a file-based program that writes text as ASCII art. You learn how to include packages in file-based programs, process command input, and read arguments either from the command line or standard input.
6667

6768
As a first step, let's write all arguments on the command line to the output. Replace the current contents of `AsciiArt.cs` with the following code:
6869

@@ -80,12 +81,13 @@ You can run this version by typing the following command:
8081
dotnet run AsciiArt.cs -- This is the command line.
8182
```
8283

83-
The `--` option indicates that all following command arguments should be passed to the AsciiArt program. The arguments `This is the command line.` are passed as an array of strings, where each string is one word: `This`, `is`, `the`, `command` and `line.`.
84+
The `--` option indicates that all following command arguments should be passed to the AsciiArt program. The arguments `This is the command line.` are passed as an array of strings, where each string is one word: `This`, `is`, `the`, `command`, and `line.`.
8485

85-
This version demonstrates several new concepts:
86+
This version demonstrates these new concepts:
8687

87-
1. The command line arguments are passed to the program using the predefined variable `args`. This 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.
88-
1. The `string.Join` method joins multiple strings into a single string, with the specfied separator. In this case, the separator is a single space.
88+
1. 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.
89+
1. The `string.Join` method joins multiple strings into a single string, with the specified separator. In this case, the separator is a single space.
90+
1. `Console.WriteLine` writes the string to the standard output console, followed by a new line.
8991

9092
That handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments. Add the following `else` clause to the `if` statement you added in the preceding code:
9193

@@ -99,7 +101,7 @@ else
99101
}
100102
```
101103

102-
You can test this by creating a new text file in the same folder. Name the file `input.txt` and add the following lines:
104+
The preceding code reads the console input until either a blank line or a `null` is read. (The `ReadLine` method returns `null` if the input stream is closed by typing <kbd>ctrl+C</kbd>.) You can test reading standard input by creating a new text file in the same folder. Name the file `input.txt` and add the following lines:
103105

104106
```text
105107
This is the input file
@@ -122,7 +124,7 @@ Now your program can accept either command line arguments or standard input.
122124

123125
> [!NOTE]
124126
>
125-
> 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.
127+
> 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.
126128
127129
On unix, you can run file-based programs directly, typing the source file name on the command line instead of `dotnet run`. You need to make two changes. First, set *execute* permissions on the source file:
128130

@@ -136,15 +138,15 @@ Then, add a `#!` directive as the first line of the `AsciiArt.cs` file:
136138
#!/usr/local/share/dotnet/dotnet run
137139
```
138140

139-
The location of `dotnet` may be different on different unix installations.
141+
The location of `dotnet` can be different on different unix installations. Use the command `whence dotnet` to local the `dotnet` host in your environment.
140142

141143
After making these two changes, you can run the program from the command line directly:
142144

143145
```bash
144146
./AsciiArt.cs
145147
```
146148

147-
If you prefer, you can rename the file to remove the extension.
149+
If you prefer, you can remove the extension so you can type `./AsciiArt` instead. You can add the `#!` to your source file even if you use Windows. The Windows command line doesn't support `#!`, but the C# compiler allows that directive in file based programs on all platforms.
148150

149151
## Add features and NuGet packages to the program
150152

@@ -156,15 +158,15 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge
156158

157159
> [!IMPORTANT]
158160
>
159-
> The version `1.2.15` was the latest version when this tutorial was last updated. If there is a newer version available, use the latest version to ensure you have the latest security packages.
161+
> The version `1.2.15` was the latest version when this tutorial was last updated. If there's a newer version available, use the latest version to ensure you have the latest security packages.
160162
161163
Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead:
162164

163165
```csharp
164166
Colorful.Console.WriteAscii(message);
165167
```
166168

167-
Next, let's add command line parsing. The current version writes each work as a different line of output. The tool doesn't support any command line options. We like to support two new features:
169+
Run the program, and you see ASCII art output instead of echoed text. Next, let's 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:
168170

169171
1. Quote multiple words that should be written on one line:
170172

@@ -178,7 +180,7 @@ Next, let's add command line parsing. The current version writes each work as a
178180
AsciiArt.cs --delay 1000
179181
```
180182

181-
Users should be able to use both.
183+
Users should be able to use both together.
182184

183185
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.
184186

@@ -197,7 +199,7 @@ To add command line parsing capabilities, first add the `System.CommandLine` pac
197199

198200
> [!IMPORTANT]
199201
>
200-
> The version `2.0.0-beta6` was the latest version when this tutorial was last updated. If there is a newer version available, use the latest version to ensure you have the latest security packages.
202+
> The version `2.0.0-beta6` was the latest version when this tutorial was last updated. If there's a newer version available, use the latest version to ensure you have the latest security packages.
201203
202204
Next, add the necessary using statements at the top of your file (after the `#!` and `#:package` directives):
203205

@@ -206,7 +208,7 @@ using System.CommandLine;
206208
using System.CommandLine.Parsing;
207209
```
208210

209-
Define the delay option and messages argument. In command-line applications, options typically begin with `--` (double dash) and can accept arguments. The `--delay` option accepts an integer argument that specifies the delay in milliseconds. The `messagesArgument` defines how any remaining tokens after options are parsed as text. Each token becomes a separate string in the array, but text can be quoted to include multiple words in one token. For example, `"This is one message"` becomes a single token, while `This is four tokens` becomes four separate tokens. Add the following code:
211+
Define the delay option and messages argument. In command-line applications, options typically begin with `--` (double dash) and can accept arguments. The `--delay` option accepts an integer argument that specifies the delay in milliseconds. The `messagesArgument` defines how any remaining tokens after options are parsed as text. Each token becomes a separate string in the array, but text can be quoted to include multiple words in one token. For example, `"This is one message"` becomes a single token, while `This is four tokens` becomes four separate tokens. Add the following code to create the <xref:System.CommandLine.Option`1?displayProperty=nameWithType> and <xref:System.CommandLine.Argument`1?displayProperty=nameWithType> objects to represent the command line option and argument:
210212

211213
```csharp
212214
Option<int> delayOption = new("--delay")
@@ -221,7 +223,7 @@ Argument<string[]> messagesArgument = new("Messages")
221223
};
222224
```
223225

224-
This application has only one root command, so you'll add the argument and option directly to the root command. Create a root command and configure it with the option and argument:
226+
The preceding code defines the argument type for the `--delay` option, and that the arguments are an array of `string` values. This application has only one command, so you use the *root command*. Create a root command and configure it with the option and argument. Add the argument and option to the root command:
225227

226228
```csharp
227229
RootCommand rootCommand = new("Ascii Art file-based program sample");
@@ -230,7 +232,7 @@ rootCommand.Options.Add(delayOption);
230232
rootCommand.Arguments.Add(messagesArgument);
231233
```
232234

233-
Parse the command line arguments and handle any errors. This validates the command line arguments and stores parsed arguments in the `ParseResult` object:
235+
Next, add the code to parse the command line arguments and handle any errors. This code validates the command line arguments and stores parsed arguments in the <xref:System.CommandLine.ParseResult?displayProperty=nameWithType> object:
234236

235237
```csharp
236238
ParseResult result = rootCommand.Parse(args);
@@ -244,15 +246,17 @@ if (result.Errors.Count > 0)
244246
}
245247
```
246248

249+
The preceding code validates all command line arguments. If the validation fails, errors are written to the console, and the app exits.
250+
247251
## Use parsed command line results
248252

249-
Now, let's finish the utility to use the parsed options and write the output. First, define a record to hold the parsed options. File-based apps can include type declarations, like records. They must be after all top-level statements and local functions. Add a `record` declaration to store the messages and the delay option value:
253+
Now, finish the app to use the parsed options and write the output. First, define a record to hold the parsed options. File-based apps can include type declarations, like records. They must be after all top-level statements and local functions. Add a `record` declaration to store the messages and the delay option value:
250254

251255
```csharp
252256
public record AsciiMessageOptions(string[] Messages, int Delay);
253257
```
254258

255-
Now that you've declared the record to store those results, add a local function to process the parse results and store the values in an instance of the record. Add the following local function before the record declaration. This method handles both command line arguments and standard input, and returns a new record instance:
259+
Now that you declared the record to store those results, add a local function to process the parse results and store the values in an instance of the record. Add the following local function before the record declaration. This method handles both command line arguments and standard input, and returns a new record instance:
256260

257261
```csharp
258262
async Task<AsciiMessageOptions> ProcessParseResults(ParseResult result)
@@ -285,7 +289,7 @@ async Task WriteAsciiArt(AsciiMessageOptions options)
285289
}
286290
```
287291

288-
Finally, replace the `if` clause you wrote earlier with the following code to write the process the command line arguments and write the output:
292+
Finally, replace the `if` clause you wrote earlier with the following code to process the command line arguments and write the output:
289293

290294
```csharp
291295
var parsedArgs = await ProcessParseResults(result);
@@ -300,7 +304,7 @@ Test the application by running several different commands. If you have trouble,
300304

301305
:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs":::
302306

303-
In this tutorial, you learned to build a file-based program, where you build the program in a single C# file. These utilities don't need a project file, and can use the `#!` directive on unix systems. These programs provide a learning platform between our [online tutorials](../../tour-of-csharp/tutorials/hello-world.md) and larger project-based programs. They also are a great platform for smaller command line utilitie.
307+
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 programs. File based programs are also a great platform for command line utilities.
304308

305309
## Related content
306310

0 commit comments

Comments
 (0)