From 60a04037c09c9eb734b260672e3eca227d572b3a Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 7 Aug 2025 16:16:53 -0400 Subject: [PATCH 01/12] finish rough draft. --- .../tutorials/file-based-programs.md | 324 ++++++++++++++++++ .../snippets/file-based-programs/AsciiArt.cs | 64 ++++ .../snippets/file-based-programs/input.txt | 8 + docs/csharp/toc.yml | 2 + 4 files changed, 398 insertions(+) create mode 100644 docs/csharp/fundamentals/tutorials/file-based-programs.md create mode 100644 docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs create mode 100644 docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md new file mode 100644 index 0000000000000..a474d9d693fe2 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -0,0 +1,324 @@ +--- +title: Build file-based programs +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. +ms.date: 08/01/2025 +ms.topic: tutorial +#customer intent: As a developer, I want build utilities so that more work is automated. +--- + +# Tutorial: Build file-based C# programs + +> [!IMPORTANT] +> +> File-based programs are a feature of .NET 10, which is in preview. +> 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. + +*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. + +In this tutorial, you: + +> [!div class="checklist"] +> +> * Create a file-based program +> * Run the program using the .NET CLI and `#!` directives +> * Add features and NuGet packages to the program +> * Test the final application + +## Prerequisites + +- The .NET 10 preview SDK. Download it [here](https://dotnet.microsoft.com/download/dotnet/10.0). +- Visual Studio Code. Download it [here](https://code.visualstudio.com/Download). +- (Optional) The C# DevKit extension for Visual Studio code. Download it [here](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit). + +If you install the C# DevKit, add this setting: + +```json +"dotnet.projects.enableFileBasedPrograms": true +``` + +Or, in the UI, it's under "Extensions", C#, LSP Server, Dotnet->Projects: Enable File Based programs". + +## Create a file-based program + +Open Visual Studio code and create a new file named `AsciiArt.cs`. Enter the following text: + +```csharp +Console.WriteLine("Hello, world!"); +``` + +Save the file. Then, open the integrated terminal in Visual Studio code and type: + +```dotnetcli +dotnet run AsciiArt.cs +``` + +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. + +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): + +```dotnetcli +AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll +``` + +On unix platforms, the output folder is something similar to: + +```dotnetcli +AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll +``` + +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. + +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. + +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. + +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: + +```csharp +if (args.Length > 0) +{ + string message = string.Join(' ', args); + Console.WriteLine(message); +} +``` + +You can run this version by typing the following command: + +```dotnetcli +dotnet run AsciiArt.cs -- This is the command line. +``` + +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.`. + +This version demonstrates several new concepts: + +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. +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. + +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: + +```csharp +else +{ + while (Console.ReadLine() is string line && line.Length > 0) + { + Console.WriteLine(line); + } +} +``` + +You can test this by creating a new text file in the same folder. Name the file `input.txt` and add the following lines: + +```text +This is the input file +for a file based program. +It prints the messages +from a file or the +command line. +There are options you +can choose for ASCII +art and colors. +``` + +Keep the lines short so they format correctly when you add the feature to use ASCII art. Now, run the program again: + +```dotnetcli +cat input.txt | dotnet run AsciiArt.cs +``` + +Now your program can accept either command line arguments or standard input. + +> [!NOTE] +> +> 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. + +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: + +```bash +chmod +x AsciiArt.cs +``` + +Then, add a `#!` directive as the first line of the `AsciiArt.cs` file: + +```csharp +#!/usr/local/share/dotnet/dotnet run +``` + +The location of `dotnet` may be different on different unix installations. + +After making these two changes, you can run the program from the command line directly: + +```bash +./AsciiArt.cs +``` + +If you prefer, you can rename the file to remove the extension. + +## Add features and NuGet packages to the program + +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. Add the following directive after the `#!` directive in your AsciiArt.cs file: + +```csharp +#:package Colorful.Console@1.2.15 +``` + +> [!IMPORTANT] +> +> 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. + +Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: + +```csharp +Colorful.Console.WriteAscii(message); +``` + +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: + +1. Quote multiple words that should be written on one line: + + ```dotnetcli + AsciiArt.cs "This is line one" "This is another line" "This is the last line" + ``` + +1. Add a `--delay` option to pause between each line: + + ```dotnetcli + AsciiArt.cs --delay 1000 + ``` + +Users should be able to use both. + +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. + +The `System.CommandLine` library offers several key benefits: + +- Automatic help text generation and validation +- Support for POSIX and Windows command-line conventions +- Built-in tab completion capabilities +- Consistent parsing behavior across applications + +To add command line parsing capabilities, first add the `System.CommandLine` package. Add this directive after the existing package directive: + +```csharp +#:package System.CommandLine@2.0.0-beta6 +``` + +> [!IMPORTANT] +> +> 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. + +Next, add the necessary using statements at the top of your file (after the `#!` and `#:package` directives): + +```csharp +using System.CommandLine; +using System.CommandLine.Parsing; +``` + +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: + +```csharp +Option delayOption = new("--delay") +{ + Description = "Delay between lines, specified as milliseconds.", + DefaultValueFactory = parseResult => 100 +}; + +Argument messagesArgument = new("Messages") +{ + Description = "Text to render." +}; +``` + +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: + +```csharp +RootCommand rootCommand = new("Ascii Art file-based program sample"); + +rootCommand.Options.Add(delayOption); +rootCommand.Arguments.Add(messagesArgument); +``` + +Parse the command line arguments and handle any errors. This validates the command line arguments and stores parsed arguments in the `ParseResult` object: + +```csharp +ParseResult result = rootCommand.Parse(args); +foreach (ParseError parseError in result.Errors) +{ + Console.Error.WriteLine(parseError.Message); +} +if (result.Errors.Count > 0) +{ + return 1; +} +``` + +## Use parsed command line results + +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: + +```csharp +public record AsciiMessageOptions(string[] Messages, int Delay); +``` + +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: + +```csharp +async Task ProcessParseResults(ParseResult result) +{ + int delay = result.GetValue(delayOption); + List messages = [.. result.GetValue(messagesArgument) ?? Array.Empty()]; + + if (messages.Count == 0) + { + while (Console.ReadLine() is string line && line.Length > 0) + { + Colorful.Console.WriteAscii(line); + await Task.Delay(delay); + } + } + return new ([..messages], delay); +} +``` + +Next, 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: + +```csharp +async Task WriteAsciiArt(AsciiMessageOptions options) +{ + foreach(string message in options.Messages) + { + Colorful.Console.WriteAscii(message); + await Task.Delay(options.Delay); + } +} +``` + +Finally, replace the `if` clause you wrote earlier with the following code to write the process the command line arguments and write the output: + +```csharp +var parsedArgs = await ProcessParseResults(result); + +await WriteAsciiArt(parsedArgs); +return 0; +``` + +## Test the final application + +Test the application by running several different commands. If you have trouble, here's the finished sample to compare with what you built: + +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs"::: + +## Next step -or- Related content + +-or- + +* [Related article title](link.md) +* [Related article title](link.md) +* [Related article title](link.md) + + diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs new file mode 100644 index 0000000000000..299a5ab908e93 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs @@ -0,0 +1,64 @@ +#!/usr/local/share/dotnet/dotnet run + +#:package Colorful.Console@1.2.15 +#:package System.CommandLine@2.0.0-beta6 + +using System.CommandLine; +using System.CommandLine.Parsing; + +Option delayOption = new("--delay") +{ + Description = "Delay between lines, specified as milliseconds.", + DefaultValueFactory = parseResult => 100 +}; + +Argument messagesArgument = new("Messages") +{ + Description = "Text to render." +}; + +RootCommand rootCommand = new("Ascii Art file-based program sample"); + +rootCommand.Options.Add(delayOption); +rootCommand.Arguments.Add(messagesArgument); +ParseResult result = rootCommand.Parse(args); +foreach (ParseError parseError in result.Errors) +{ + Console.Error.WriteLine(parseError.Message); +} +if (result.Errors.Count > 0) +{ + return 1; +} + +var parsedArgs = await ProcessParseResults(result); + +await WriteAsciiArt(parsedArgs); +return 0; + +async Task ProcessParseResults(ParseResult result) +{ + int delay = result.GetValue(delayOption); + List messages = [.. result.GetValue(messagesArgument) ?? Array.Empty()]; + + if (messages.Count == 0) + { + while (Console.ReadLine() is string line && line.Length > 0) + { + Colorful.Console.WriteAscii(line); + await Task.Delay(delay); + } + } + return new ([..messages], delay); +} + +async Task WriteAsciiArt(AsciiMessageOptions options) +{ + foreach(string message in options.Messages) + { + Colorful.Console.WriteAscii(message); + await Task.Delay(options.Delay); + } +} + +public record AsciiMessageOptions(string[] Messages, int Delay); diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt new file mode 100644 index 0000000000000..377e1cd8c2109 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt @@ -0,0 +1,8 @@ +This is the input file +for a file based program. +It prints the messages +from a file or the +command line. +There are options you +can choose for ASCII +art and colors. diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index 7a89acd91ef69..a7bd3d67b256a 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -108,6 +108,8 @@ items: href: fundamentals/coding-style/coding-conventions.md - name: Tutorials items: + - name: Build file-based programs + href: fundamentals/tutorials/file-based-programs.md - name: "How to display command-line arguments" href: fundamentals/tutorials/how-to-display-command-line-arguments.md # TODO: Command line processing tutorials From a5a49d4a4aaa2aab57983d7eb88a324ca5e6b22f Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 7 Aug 2025 16:45:12 -0400 Subject: [PATCH 02/12] Set executable perms, fix warnings set the `+x` permission on the file-based program. Fix build warnings. --- .../tutorials/file-based-programs.md | 21 +++++++------------ .../snippets/file-based-programs/AsciiArt.cs | 0 2 files changed, 7 insertions(+), 14 deletions(-) mode change 100644 => 100755 docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index a474d9d693fe2..3e1453685f3e4 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -1,7 +1,7 @@ --- title: Build file-based programs 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. -ms.date: 08/01/2025 +ms.date: 08/08/2025 ms.topic: tutorial #customer intent: As a developer, I want build utilities so that more work is automated. --- @@ -188,7 +188,7 @@ Next, let's add command line parsing. The current version writes each work as a Users should be able to use both. -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. +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. The `System.CommandLine` library offers several key benefits: @@ -308,17 +308,10 @@ Test the application by running several different commands. If you have trouble, :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs"::: -## Next step -or- Related content +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. --or- +## Related content -* [Related article title](link.md) -* [Related article title](link.md) -* [Related article title](link.md) - - +- [Top level statement](../program-structure/top-level-statements.md) +- [Preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-programs) +- [What's new in C# 14](../../whats-new/csharp-14.md) diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs old mode 100644 new mode 100755 From 41e47a421a875167ae0207932a35b5b9d6d10b3b Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 7 Aug 2025 16:51:44 -0400 Subject: [PATCH 03/12] build warnings 2 --- .../fundamentals/tutorials/file-based-programs.md | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 3e1453685f3e4..0a767082173ec 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -26,17 +26,9 @@ In this tutorial, you: ## Prerequisites -- The .NET 10 preview SDK. Download it [here](https://dotnet.microsoft.com/download/dotnet/10.0). -- Visual Studio Code. Download it [here](https://code.visualstudio.com/Download). -- (Optional) The C# DevKit extension for Visual Studio code. Download it [here](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit). - -If you install the C# DevKit, add this setting: - -```json -"dotnet.projects.enableFileBasedPrograms": true -``` - -Or, in the UI, it's under "Extensions", C#, LSP Server, Dotnet->Projects: Enable File Based programs". +- The .NET 10 preview SDK. Download it from the [.NET download site](https://dotnet.microsoft.com/download/dotnet/10.0). +- Visual Studio Code. Download it from the [Visual Studio Code homepage](https://code.visualstudio.com/Download). +- (Optional) The C# DevKit extension for Visual Studio Code. Download it from the [Visual Studio Code marketplace](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit). ## Create a file-based program From 40ba7e3f8fab29e5a7d6350783ef33dc1dcbc912 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Aug 2025 11:59:15 -0400 Subject: [PATCH 04/12] major edit pass --- .../tutorials/file-based-programs.md | 66 ++++++++++--------- 1 file changed, 35 insertions(+), 31 deletions(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 0a767082173ec..6075d1f5b115b 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -3,6 +3,7 @@ title: Build file-based programs 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. ms.date: 08/08/2025 ms.topic: tutorial +ai-usage: ai-assisted #customer intent: As a developer, I want build utilities so that more work is automated. --- @@ -11,7 +12,7 @@ ms.topic: tutorial > [!IMPORTANT] > > File-based programs are a feature of .NET 10, which is in preview. -> 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. +> 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. *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. @@ -19,10 +20,10 @@ In this tutorial, you: > [!div class="checklist"] > -> * Create a file-based program -> * Run the program using the .NET CLI and `#!` directives -> * Add features and NuGet packages to the program -> * Test the final application +> * Create a file-based program. +> * Run the program using the .NET CLI and `#!` directives. +> * Add features and NuGet packages to the program. +> * Parse and process command line arguments and standard input. ## Prerequisites @@ -32,21 +33,21 @@ In this tutorial, you: ## Create a file-based program -Open Visual Studio code and create a new file named `AsciiArt.cs`. Enter the following text: +Open Visual Studio Code and create a new file named `AsciiArt.cs`. Enter the following text: ```csharp Console.WriteLine("Hello, world!"); ``` -Save the file. Then, open the integrated terminal in Visual Studio code and type: +Save the file. Then, open the integrated terminal in Visual Studio Code and type: ```dotnetcli dotnet run AsciiArt.cs ``` -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. +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. -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): +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): ```dotnetcli 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: AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc/bin/debug/AsciiArt.dll ``` -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. +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. -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. +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. -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. +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. 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: @@ -80,12 +81,13 @@ You can run this version by typing the following command: dotnet run AsciiArt.cs -- This is the command line. ``` -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.`. +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.`. -This version demonstrates several new concepts: +This version demonstrates these new concepts: -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. -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. +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. +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. +1. `Console.WriteLine` writes the string to the standard output console, followed by a new line. 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: @@ -99,7 +101,7 @@ else } ``` -You can test this by creating a new text file in the same folder. Name the file `input.txt` and add the following lines: +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 ctrl+C.) 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: ```text This is the input file @@ -122,7 +124,7 @@ Now your program can accept either command line arguments or standard input. > [!NOTE] > -> 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. +> 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. 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: @@ -136,7 +138,7 @@ Then, add a `#!` directive as the first line of the `AsciiArt.cs` file: #!/usr/local/share/dotnet/dotnet run ``` -The location of `dotnet` may be different on different unix installations. +The location of `dotnet` can be different on different unix installations. Use the command `whence dotnet` to local the `dotnet` host in your environment. After making these two changes, you can run the program from the command line directly: @@ -144,7 +146,7 @@ After making these two changes, you can run the program from the command line di ./AsciiArt.cs ``` -If you prefer, you can rename the file to remove the extension. +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. ## Add features and NuGet packages to the program @@ -156,7 +158,7 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge > [!IMPORTANT] > -> 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. +> 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. Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: @@ -164,7 +166,7 @@ Next, change the lines that call `Console.WriteLine` to use the `Colorful.Consol Colorful.Console.WriteAscii(message); ``` -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: +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: 1. Quote multiple words that should be written on one line: @@ -178,7 +180,7 @@ Next, let's add command line parsing. The current version writes each work as a AsciiArt.cs --delay 1000 ``` -Users should be able to use both. +Users should be able to use both together. 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. @@ -197,7 +199,7 @@ To add command line parsing capabilities, first add the `System.CommandLine` pac > [!IMPORTANT] > -> 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. +> 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. Next, add the necessary using statements at the top of your file (after the `#!` and `#:package` directives): @@ -206,7 +208,7 @@ using System.CommandLine; using System.CommandLine.Parsing; ``` -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: +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 and objects to represent the command line option and argument: ```csharp Option delayOption = new("--delay") @@ -221,7 +223,7 @@ Argument messagesArgument = new("Messages") }; ``` -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: +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: ```csharp RootCommand rootCommand = new("Ascii Art file-based program sample"); @@ -230,7 +232,7 @@ rootCommand.Options.Add(delayOption); rootCommand.Arguments.Add(messagesArgument); ``` -Parse the command line arguments and handle any errors. This validates the command line arguments and stores parsed arguments in the `ParseResult` object: +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 object: ```csharp ParseResult result = rootCommand.Parse(args); @@ -244,15 +246,17 @@ if (result.Errors.Count > 0) } ``` +The preceding code validates all command line arguments. If the validation fails, errors are written to the console, and the app exits. + ## Use parsed command line results -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: +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: ```csharp public record AsciiMessageOptions(string[] Messages, int Delay); ``` -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: +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: ```csharp async Task ProcessParseResults(ParseResult result) @@ -285,7 +289,7 @@ async Task WriteAsciiArt(AsciiMessageOptions options) } ``` -Finally, replace the `if` clause you wrote earlier with the following code to write the process the command line arguments and write the output: +Finally, replace the `if` clause you wrote earlier with the following code to process the command line arguments and write the output: ```csharp var parsedArgs = await ProcessParseResults(result); @@ -300,7 +304,7 @@ Test the application by running several different commands. If you have trouble, :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs"::: -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. +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. ## Related content From e3441d0dde98c90063625bfd7eb9e4462759f00d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Aug 2025 13:31:19 -0400 Subject: [PATCH 05/12] Extract code snippets Use extracted code snippets instead of inline code. --- .../tutorials/file-based-programs.md | 88 +++---------------- .../snippets/file-based-programs/AsciiArt.cs | 27 +++++- 2 files changed, 36 insertions(+), 79 deletions(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 6075d1f5b115b..575baa25487b9 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -152,9 +152,7 @@ If you prefer, you can remove the extension so you can type `./AsciiArt` instead 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. Add the following directive after the `#!` directive in your AsciiArt.cs file: -```csharp -#:package Colorful.Console@1.2.15 -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ColorfulPackage"::: > [!IMPORTANT] > @@ -162,9 +160,7 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: -```csharp -Colorful.Console.WriteAscii(message); -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: 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: @@ -193,9 +189,7 @@ The `System.CommandLine` library offers several key benefits: To add command line parsing capabilities, first add the `System.CommandLine` package. Add this directive after the existing package directive: -```csharp -#:package System.CommandLine@2.0.0-beta6 -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="CommandLinePackage"::: > [!IMPORTANT] > @@ -203,48 +197,20 @@ To add command line parsing capabilities, first add the `System.CommandLine` pac Next, add the necessary using statements at the top of your file (after the `#!` and `#:package` directives): -```csharp -using System.CommandLine; -using System.CommandLine.Parsing; -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Usings"::: 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 and objects to represent the command line option and argument: -```csharp -Option delayOption = new("--delay") -{ - Description = "Delay between lines, specified as milliseconds.", - DefaultValueFactory = parseResult => 100 -}; - -Argument messagesArgument = new("Messages") -{ - Description = "Text to render." -}; -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="OptionArgument"::: 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: -```csharp -RootCommand rootCommand = new("Ascii Art file-based program sample"); +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="RootCommand"::: -rootCommand.Options.Add(delayOption); -rootCommand.Arguments.Add(messagesArgument); -``` 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 object: -```csharp -ParseResult result = rootCommand.Parse(args); -foreach (ParseError parseError in result.Errors) -{ - Console.Error.WriteLine(parseError.Message); -} -if (result.Errors.Count > 0) -{ - return 1; -} -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ParseAndValidate"::: The preceding code validates all command line arguments. If the validation fails, errors are written to the console, and the app exits. @@ -252,51 +218,19 @@ The preceding code validates all command line arguments. If the validation fails 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: -```csharp -public record AsciiMessageOptions(string[] Messages, int Delay); -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Record"::: 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: -```csharp -async Task ProcessParseResults(ParseResult result) -{ - int delay = result.GetValue(delayOption); - List messages = [.. result.GetValue(messagesArgument) ?? Array.Empty()]; - - if (messages.Count == 0) - { - while (Console.ReadLine() is string line && line.Length > 0) - { - Colorful.Console.WriteAscii(line); - await Task.Delay(delay); - } - } - return new ([..messages], delay); -} -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ProcessParsedArgs"::: Next, 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: -```csharp -async Task WriteAsciiArt(AsciiMessageOptions options) -{ - foreach(string message in options.Messages) - { - Colorful.Console.WriteAscii(message); - await Task.Delay(options.Delay); - } -} -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: Finally, replace the `if` clause you wrote earlier with the following code to process the command line arguments and write the output: -```csharp -var parsedArgs = await ProcessParseResults(result); - -await WriteAsciiArt(parsedArgs); -return 0; -``` +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="InvokeCommand"::: ## Test the final application diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs index 299a5ab908e93..24ba5db893246 100755 --- a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs @@ -1,11 +1,18 @@ #!/usr/local/share/dotnet/dotnet run +// #:package Colorful.Console@1.2.15 +// +// #:package System.CommandLine@2.0.0-beta6 +// +// using System.CommandLine; using System.CommandLine.Parsing; +// +// Option delayOption = new("--delay") { Description = "Delay between lines, specified as milliseconds.", @@ -16,11 +23,16 @@ { Description = "Text to render." }; +// +// RootCommand rootCommand = new("Ascii Art file-based program sample"); rootCommand.Options.Add(delayOption); rootCommand.Arguments.Add(messagesArgument); +// + +// ParseResult result = rootCommand.Parse(args); foreach (ParseError parseError in result.Errors) { @@ -30,12 +42,16 @@ { return 1; } +// +// var parsedArgs = await ProcessParseResults(result); await WriteAsciiArt(parsedArgs); return 0; +// +// async Task ProcessParseResults(ParseResult result) { int delay = result.GetValue(delayOption); @@ -45,20 +61,27 @@ async Task ProcessParseResults(ParseResult result) { while (Console.ReadLine() is string line && line.Length > 0) { + // Colorful.Console.WriteAscii(line); + // await Task.Delay(delay); } } - return new ([..messages], delay); + return new([.. messages], delay); } +// +// async Task WriteAsciiArt(AsciiMessageOptions options) { - foreach(string message in options.Messages) + foreach (string message in options.Messages) { Colorful.Console.WriteAscii(message); await Task.Delay(options.Delay); } } +// +// public record AsciiMessageOptions(string[] Messages, int Delay); +// From 1491bdba3129a4b8a2f8e9bb78779656ebd14453 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 8 Aug 2025 13:39:29 -0400 Subject: [PATCH 06/12] fix warning --- docs/csharp/fundamentals/tutorials/file-based-programs.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 575baa25487b9..2cc76820a9de6 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -207,7 +207,6 @@ The preceding code defines the argument type for the `--delay` option, and that :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="RootCommand"::: - 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 object: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ParseAndValidate"::: From 9d1b4d876b77d8dd0930161bbe57fca0184cd255 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Aug 2025 15:34:44 -0400 Subject: [PATCH 07/12] Apply suggestions from code review Co-authored-by: Andy (Steve) De George <67293991+adegeo@users.noreply.github.com> --- .../tutorials/file-based-programs.md | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 2cc76820a9de6..6cba5e0c21447 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -10,11 +10,10 @@ ai-usage: ai-assisted # Tutorial: Build file-based C# programs > [!IMPORTANT] -> > File-based programs are a feature of .NET 10, which is in preview. > 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. -*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. +*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 run using `#!` (shebang) directives. In this tutorial, you: @@ -45,9 +44,9 @@ Save the file. Then, open the integrated terminal in Visual Studio Code and type dotnet run AsciiArt.cs ``` -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. +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 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): +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 displayed when you built the program should look something like this (on Windows): ```dotnetcli AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll @@ -85,9 +84,9 @@ The `--` option indicates that all following command arguments should be passed This version demonstrates these new concepts: -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. -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. -1. `Console.WriteLine` writes the string to the standard output console, followed by a new line. +- 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. +- The `string.Join` method joins multiple strings into a single string, with the specified separator. In this case, the separator is a single space. +- `Console.WriteLine` writes the string to the standard output console, followed by a new line. 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: @@ -156,13 +155,13 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge > [!IMPORTANT] > -> 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. +> The version `1.2.15` was the latest version of the `Colorful.Console` package when this tutorial was last updated. Check the package's [NuGet page](https://www.nuget.org/packages/Colorful.Console) for the latest version to ensure you use a package version with the latest security fixes. Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: -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: +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 added support two features: 1. Quote multiple words that should be written on one line: @@ -176,16 +175,16 @@ Run the program, and you see ASCII art output instead of echoed text. Next, let' AsciiArt.cs --delay 1000 ``` -Users should be able to use both together. +Users should be able to use both arguments together. 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. The `System.CommandLine` library offers several key benefits: -- Automatic help text generation and validation -- Support for POSIX and Windows command-line conventions -- Built-in tab completion capabilities -- Consistent parsing behavior across applications +- Automatic help text generation and validation. +- Support for POSIX and Windows command-line conventions. +- Built-in tab completion capabilities. +- Consistent parsing behavior across applications. To add command line parsing capabilities, first add the `System.CommandLine` package. Add this directive after the existing package directive: @@ -215,7 +214,7 @@ The preceding code validates all command line arguments. If the validation fails ## Use parsed command line results -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: +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 and classes. They must be after all top-level statements and local functions. Add a `record` declaration to store the messages and the delay option value: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Record"::: From 44999294235ee6b7c30a97aaad6f8b09a87a9d73 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Aug 2025 17:06:55 -0400 Subject: [PATCH 08/12] Partial review updates. --- docfx.json | 2 +- docs/core/whats-new/dotnet-10/sdk.md | 2 +- .../fundamentals/program-structure/index.md | 4 +-- .../program-structure/main-command-line.md | 2 +- .../program-structure/top-level-statements.md | 2 +- .../tutorials/file-based-programs.md | 28 ++++++++++--------- .../snippets/file-based-programs/input.txt | 2 +- .../preprocessor-directives.md | 6 ++-- docs/csharp/toc.yml | 2 +- docs/csharp/tour-of-csharp/overview.md | 6 ++-- 10 files changed, 29 insertions(+), 27 deletions(-) diff --git a/docfx.json b/docfx.json index a4a2fa1fc0dae..c6ca578234c4c 100644 --- a/docfx.json +++ b/docfx.json @@ -835,7 +835,7 @@ "_csharplang/proposals/csharp-14.0/null-conditional-assignment.md": "This proposal allows the null conditional operator to be used for the destination of assignment expressions. This allows you to assign a value to a property or field only if the left side is not null.", "_csharplang/proposals/csharp-14.0/extensions.md": "This proposal enables new kinds of extension members. These new extension members support extension properties, extension static members, including extension operators.", "_csharplang/proposals/csharp-14.0/user-defined-compound-assignment.md": "This proposal introduces user-defined compound assignment operators. Developers can override compound assignment, increment, and decrement operators.", - "_csharplang/proposals/csharp-14.0/ignored-directives.md": "This proposal allows a source file to include ignored directives. In most cases, ignored directives are used for file based programs, for example `#!`", + "_csharplang/proposals/csharp-14.0/ignored-directives.md": "This proposal allows a source file to include ignored directives. In most cases, ignored directives are used for file-based apps, for example `#!`", "_csharplang/proposals/csharp-14.0/extension-operators.md": "This proposal extends the proposal for extensions to include *extension operators*, where an operator can be an extension member.", "_csharplang/proposals/csharp-14.0/named-and-optional-parameters-in-expression-trees.md": "This proposal allows an expression tree to include named and optional parameters. This enables expression trees to be more flexible in how they are constructed.", "_roslyn/docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md": "Learn about any breaking changes since the initial release of C# 10 and included in C# 11", diff --git a/docs/core/whats-new/dotnet-10/sdk.md b/docs/core/whats-new/dotnet-10/sdk.md index 6eb8d37e46818..2f76811513b62 100644 --- a/docs/core/whats-new/dotnet-10/sdk.md +++ b/docs/core/whats-new/dotnet-10/sdk.md @@ -113,7 +113,7 @@ The output provides a structured, machine-readable description of the command's ## File-based apps enhancements -.NET 10 brings significant updates to the file-based apps experience, including publish support and native AOT capabilities. For an introduction to file-based programs, see [File based programs](../../../csharp/tour-of-csharp/overview.md#file-based-programs) and [Building and running C# programs](../../../csharp/fundamentals/program-structure/index.md#building-and-running-c-programs). +.NET 10 brings significant updates to the file-based apps experience, including publish support and native AOT capabilities. For an introduction to file-based apps, see [File-based apps](../../../csharp/tour-of-csharp/overview.md#file-based-programs) and [Building and running C# programs](../../../csharp/fundamentals/program-structure/index.md#building-and-running-c-programs). ### Enhanced file-based apps with publish support and native AOT diff --git a/docs/csharp/fundamentals/program-structure/index.md b/docs/csharp/fundamentals/program-structure/index.md index 33067c5d2802f..aa424b5194c6d 100644 --- a/docs/csharp/fundamentals/program-structure/index.md +++ b/docs/csharp/fundamentals/program-structure/index.md @@ -22,7 +22,7 @@ In that case the program starts in the opening brace of `Main` method, which is C# is a *compiled* language. In most C# programs, you use the [`dotnet build`](../../../core/tools/dotnet-build.md) command to compile a group of source files into a binary package. Then, you use the [`dotnet run`](../../../core/tools/dotnet-run.md) command to run the program. (You can simplify this process because `dotnet run` compiles the program before running it if necessary.) These tools support a rich language of configuration options and command-line switches. The `dotnet` command line interface (CLI), which is included in the .NET SDK, provides many [tools](../../../core/tools/index.md) to generate and modify C# files. -Beginning with C# 14 and .NET 10, you can create *file based programs*, which simplifies building and running C# programs. You use the `dotnet run` command to run a program contained in a single `*.cs` file. For example, if the following snippet is stored in a file named `hello-world.cs`, you can run it by typing `dotnet run hello-world.cs`: +Beginning with C# 14 and .NET 10, you can create *file-based apps*, which simplifies building and running C# programs. You use the `dotnet run` command to run a program contained in a single `*.cs` file. For example, if the following snippet is stored in a file named `hello-world.cs`, you can run it by typing `dotnet run hello-world.cs`: :::code language="csharp" source="./snippets/file-based-program/hello-world.cs"::: @@ -32,7 +32,7 @@ The first line of the program contains the `#!` sequence for Unix shells. The lo ./hello-world.cs ``` -The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file based programs for small command-line utilities, prototypes, or other experiments. File based programs allow [preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-programs) that configure the build system. +The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file-based apps for small command-line utilities, prototypes, or other experiments. file-based apps allow [preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-programs) that configure the build system. ## Expressions and statements diff --git a/docs/csharp/fundamentals/program-structure/main-command-line.md b/docs/csharp/fundamentals/program-structure/main-command-line.md index e32e9ed00df2f..9821df757dc56 100644 --- a/docs/csharp/fundamentals/program-structure/main-command-line.md +++ b/docs/csharp/fundamentals/program-structure/main-command-line.md @@ -24,7 +24,7 @@ You can also use top-level statements in one file as the entry point for your ap :::code language="csharp" source="snippets/top-level-statements-1/Program.cs"::: -Beginning with C# 14, programs can be [*file based programs*](./index.md#building-and-running-c-programs), where a single file contains the program. You run *file based programs* with the command `dotnet run `, or using the `#!/usr/local/share/dotnet/dotnet run` directive as the first line (unix shells only). +Beginning with C# 14, programs can be [*file-based apps*](./index.md#building-and-running-c-programs), where a single file contains the program. You run *file-based apps* with the command `dotnet run `, or using the `#!/usr/local/share/dotnet/dotnet run` directive as the first line (unix shells only). ## Overview diff --git a/docs/csharp/fundamentals/program-structure/top-level-statements.md b/docs/csharp/fundamentals/program-structure/top-level-statements.md index 3396d27a69988..743c695a5301e 100644 --- a/docs/csharp/fundamentals/program-structure/top-level-statements.md +++ b/docs/csharp/fundamentals/program-structure/top-level-statements.md @@ -85,7 +85,7 @@ The compiler generates a method to serve as the program entry point for a projec | `return` | `static int Main(string[] args)` | | No `await` or `return` | `static void Main(string[] args)` | -Beginning with C# 14, programs can be [*file based programs*](./index.md#building-and-running-c-programs), where a single file contains the program. You run *file based programs* with the command `dotnet run `, or using the `#!/usr/local/share/dotnet/dotnet run` directive as the first line (unix shells only). +Beginning with C# 14, programs can be [*file-based apps*](./index.md#building-and-running-c-programs), where a single file contains the program. You run *file-based apps* with the command `dotnet run `, or using the `#!/usr/local/share/dotnet/dotnet run` directive as the first line (unix shells only). ## C# language specification diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 6cba5e0c21447..05d609e4e0c6c 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -1,6 +1,6 @@ --- -title: Build file-based programs -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. +title: Build file-based apps +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. ms.date: 08/08/2025 ms.topic: tutorial ai-usage: ai-assisted @@ -10,10 +10,10 @@ ai-usage: ai-assisted # Tutorial: Build file-based C# programs > [!IMPORTANT] -> File-based programs are a feature of .NET 10, which is in preview. +> File-based apps are a feature of .NET 10, which is in preview. > 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. -*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 run using `#!` (shebang) directives. +*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. In this tutorial, you: @@ -24,6 +24,8 @@ In this tutorial, you: > * Add features and NuGet packages to the program. > * Parse and process command line arguments and standard input. +You build a file-based program that writes text as ASCII art. It's declared in a single file, uses NuGet packages that implement some of the core features. + ## Prerequisites - The .NET 10 preview SDK. Download it from the [.NET download site](https://dotnet.microsoft.com/download/dotnet/10.0). @@ -46,7 +48,7 @@ dotnet run AsciiArt.cs 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 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 displayed when you built the program should look something like this (on Windows): +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): ```dotnetcli AsciiArt succeeded (7.3s) → AppData\Local\Temp\dotnet\runfile\AsciiArt-85c58ae0cd68371711f06f297fa0d7891d0de82afde04d8c64d5f910ddc04ddc\bin\debug\AsciiArt.dll @@ -60,9 +62,9 @@ AsciiArt succeeded (7.3s) → Library/Application Support/dotnet/runfile/AsciiAr 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. -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. +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. -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. +## Process command line arguments 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: @@ -104,7 +106,7 @@ The preceding code reads the console input until either a blank line or a `null` ```text This is the input file -for a file based program. +for a file-based program. It prints the messages from a file or the command line. @@ -125,7 +127,7 @@ Now your program can accept either command line arguments or standard input. > > 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. -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: +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. First, set *execute* permissions on the source file: ```bash chmod +x AsciiArt.cs @@ -145,11 +147,11 @@ After making these two changes, you can run the program from the command line di ./AsciiArt.cs ``` -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. +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 apps on all platforms. ## Add features and NuGet packages to the program -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. Add the following directive after the `#!` directive in your AsciiArt.cs file: +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. Add the following directive after the `#!` directive in your AsciiArt.cs file: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ColorfulPackage"::: @@ -234,9 +236,9 @@ Finally, replace the `if` clause you wrote earlier with the following code to pr Test the application by running several different commands. If you have trouble, here's the finished sample to compare with what you built: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs"::: +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" Lines="1-88"::: -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. +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. ## Related content diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt index 377e1cd8c2109..874d5bcd9bbd4 100644 --- a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt @@ -1,5 +1,5 @@ This is the input file -for a file based program. +for a file-based program. It prints the messages from a file or the command line. diff --git a/docs/csharp/language-reference/preprocessor-directives.md b/docs/csharp/language-reference/preprocessor-directives.md index 1c4057ff78a31..ec1d766d85caa 100644 --- a/docs/csharp/language-reference/preprocessor-directives.md +++ b/docs/csharp/language-reference/preprocessor-directives.md @@ -47,9 +47,9 @@ helpviewer_keywords: Although the compiler doesn't have a separate preprocessor, the directives described in this section are processed as if there were one. You use them to help in conditional compilation. Unlike C and C++ directives, you can't use these directives to create macros. A preprocessor directive must be the only instruction on a line. -## File based programs +## File-based apps -*File based programs* are programs that are compiled and run using `dotnet run Program.cs` (or any `*.cs` file). The C# compiler ignores these preprocessor directives, but the build system parses them to produce the output. These directives generate warnings when encountered in a project-based compilation. +*File-based apps* are programs that are compiled and run using `dotnet run Program.cs` (or any `*.cs` file). The C# compiler ignores these preprocessor directives, but the build system parses them to produce the output. These directives generate warnings when encountered in a project-based compilation. The C# compiler ignores any preprocessor directive that starts with `#:` or `#!`. @@ -62,7 +62,7 @@ Console.WriteLine("Hello"); The preceding code snippet informs a Unix shell to execute the file using `/usr/local/share/dotnet/dotnet run`. (Your installation directory for the `dotnet` CLI can be different on different Unix or 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. -The `#:` directives that are used in file based programs include: +The `#:` directives that are used in file-based apps include: - `#:sdk`: diff --git a/docs/csharp/toc.yml b/docs/csharp/toc.yml index a7bd3d67b256a..a97f1a3196ae7 100644 --- a/docs/csharp/toc.yml +++ b/docs/csharp/toc.yml @@ -108,7 +108,7 @@ items: href: fundamentals/coding-style/coding-conventions.md - name: Tutorials items: - - name: Build file-based programs + - name: Build file-based apps href: fundamentals/tutorials/file-based-programs.md - name: "How to display command-line arguments" href: fundamentals/tutorials/how-to-display-command-line-arguments.md diff --git a/docs/csharp/tour-of-csharp/overview.md b/docs/csharp/tour-of-csharp/overview.md index 76d4fbaa509e0..582c430ef2d1f 100644 --- a/docs/csharp/tour-of-csharp/overview.md +++ b/docs/csharp/tour-of-csharp/overview.md @@ -34,11 +34,11 @@ The `Program` class declared by the "Hello, World" program has a single member, > [!TIP] > The examples in this article give you a first look at C# code. Some samples might show elements of C# that you're not familiar with. When you're ready to learn C#, start with our [beginner tutorials](./tutorials/index.md), or dive into the links in each section. If you're experienced in [Java](./tips-for-java-developers.md), [JavaScript](./tips-for-javascript-developers.md), [TypeScript](./tips-for-javascript-developers.md), or [Python](./tips-for-python-developers.md), read our tips to help you find the information you need to quickly learn C#. -## File based programs +## File-based apps C# is a *compiled* language. In most C# programs, you use the [`dotnet build`](../../core/tools/dotnet-build.md) command to compile a group of source files into a binary package. Then, you use the [`dotnet run`](../../core/tools/dotnet-run.md) command to run the program. (You can simplify this process because `dotnet run` compiles the program before running it if necessary.) These tools support a rich language of configuration options and command-line switches. The `dotnet` command line interface (CLI), which is included in the .NET SDK, provides many [tools](../../core/tools/index.md) to generate and modify C# files. -Beginning with C# 14 and .NET 10, you can create *file based programs*, which simplifies building and running C# programs. You use the `dotnet run` command to run a program contained in a single `*.cs` file. For example, if the following code is stored in a file named `hello-world.cs`, you can run it by typing `dotnet run hello-world.cs`: +Beginning with C# 14 and .NET 10, you can create *file-based apps*, which simplifies building and running C# programs. You use the `dotnet run` command to run a program contained in a single `*.cs` file. For example, if the following code is stored in a file named `hello-world.cs`, you can run it by typing `dotnet run hello-world.cs`: :::code language="csharp" source="./snippets/file-based-programs/hello-world.cs"::: @@ -48,7 +48,7 @@ The first line of the program contains the `#!` sequence (shebang) for unix shel ./hello-world.cs ``` -The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file based programs for small command-line utilities, prototypes, or other experiments. +The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file-based apps for small command-line utilities, prototypes, or other experiments. ## Familiar C# features From 465e1a76ee40b28a1da69b49255308beabad0302 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Aug 2025 17:23:34 -0400 Subject: [PATCH 09/12] Update file-based-programs.md Fix build error --- docs/csharp/fundamentals/tutorials/file-based-programs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 05d609e4e0c6c..f0b7452f0117b 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -236,12 +236,12 @@ Finally, replace the `if` clause you wrote earlier with the following code to pr Test the application by running several different commands. If you have trouble, here's the finished sample to compare with what you built: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" Lines="1-88"::: +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" range="1-88"::: 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. ## Related content - [Top level statement](../program-structure/top-level-statements.md) -- [Preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-programs) +- [Preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-apps) - [What's new in C# 14](../../whats-new/csharp-14.md) From 358e4e8c82fb8003b8ede4c6f89a8ec072692072 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Tue, 12 Aug 2025 17:27:22 -0400 Subject: [PATCH 10/12] fix warnings --- docs/core/whats-new/dotnet-10/sdk.md | 2 +- docs/csharp/fundamentals/program-structure/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/whats-new/dotnet-10/sdk.md b/docs/core/whats-new/dotnet-10/sdk.md index 2f76811513b62..d1ad48ef454c2 100644 --- a/docs/core/whats-new/dotnet-10/sdk.md +++ b/docs/core/whats-new/dotnet-10/sdk.md @@ -113,7 +113,7 @@ The output provides a structured, machine-readable description of the command's ## File-based apps enhancements -.NET 10 brings significant updates to the file-based apps experience, including publish support and native AOT capabilities. For an introduction to file-based apps, see [File-based apps](../../../csharp/tour-of-csharp/overview.md#file-based-programs) and [Building and running C# programs](../../../csharp/fundamentals/program-structure/index.md#building-and-running-c-programs). +.NET 10 brings significant updates to the file-based apps experience, including publish support and native AOT capabilities. For an introduction to file-based apps, see [File-based apps](../../../csharp/tour-of-csharp/overview.md#file-based-apps) and [Building and running C# programs](../../../csharp/fundamentals/program-structure/index.md#building-and-running-c-programs). ### Enhanced file-based apps with publish support and native AOT diff --git a/docs/csharp/fundamentals/program-structure/index.md b/docs/csharp/fundamentals/program-structure/index.md index aa424b5194c6d..5a8a0a88ac7ca 100644 --- a/docs/csharp/fundamentals/program-structure/index.md +++ b/docs/csharp/fundamentals/program-structure/index.md @@ -32,7 +32,7 @@ The first line of the program contains the `#!` sequence for Unix shells. The lo ./hello-world.cs ``` -The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file-based apps for small command-line utilities, prototypes, or other experiments. file-based apps allow [preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-programs) that configure the build system. +The source for these programs must be a single file, but otherwise all C# syntax is valid. You can use file-based apps for small command-line utilities, prototypes, or other experiments. file-based apps allow [preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-apps) that configure the build system. ## Expressions and statements From 103be15e7e1f11d9222109bf2bea357d9706ed8d Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 14 Aug 2025 15:45:19 -0400 Subject: [PATCH 11/12] respond to feedback. --- .../tutorials/file-based-programs.md | 227 ++++++++++-------- .../snippets/file-based-programs/AsciiArt | 67 ++++++ .../snippets/file-based-programs/input.txt | 18 +- 3 files changed, 203 insertions(+), 109 deletions(-) create mode 100644 docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index f0b7452f0117b..17a97808c30b2 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -1,7 +1,7 @@ --- title: Build file-based apps 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. -ms.date: 08/08/2025 +ms.date: 08/14/2025 ms.topic: tutorial ai-usage: ai-assisted #customer intent: As a developer, I want build utilities so that more work is automated. @@ -24,7 +24,7 @@ In this tutorial, you: > * Add features and NuGet packages to the program. > * Parse and process command line arguments and standard input. -You build a file-based program that writes text as ASCII art. It's declared in a single file, uses NuGet packages that implement some of the core features. +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. ## Prerequisites @@ -34,17 +34,17 @@ You build a file-based program that writes text as ASCII art. It's declared in a ## Create a file-based program -Open Visual Studio Code and create a new file named `AsciiArt.cs`. Enter the following text: +1. Open Visual Studio Code and create a new file named `AsciiArt.cs`. Enter the following text: -```csharp -Console.WriteLine("Hello, world!"); -``` + ```csharp + Console.WriteLine("Hello, world!"); + ``` -Save the file. Then, open the integrated terminal in Visual Studio Code and type: +1. Save the file. Then, open the integrated terminal in Visual Studio Code and type: -```dotnetcli -dotnet run AsciiArt.cs -``` + ```dotnetcli + dotnet run AsciiArt.cs + ``` 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. @@ -64,106 +64,124 @@ That output tells you where the temporary files and build outputs are placed. Th 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. -## Process command line arguments +## Unix shebang (`#!`) support -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: +> [!NOTE] +> +> 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. -```csharp -if (args.Length > 0) -{ - string message = string.Join(' ', args); - Console.WriteLine(message); -} -``` +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: -You can run this version by typing the following command: +1. Set *execute* permissions on the source file: -```dotnetcli -dotnet run AsciiArt.cs -- This is the command line. + ```bash + chmod +x AsciiArt.cs + ``` + +2. Add a shebang (`#!`) directive as the first line of the `AsciiArt.cs` file: + + ```csharp + #!/usr/local/share/dotnet/dotnet run + ``` + +The location of `dotnet` can be different on different unix installations. Use the command `whence dotnet` to local the `dotnet` host in your environment. + +After making these two changes, you can run the program from the command line directly: + +```bash +./AsciiArt.cs ``` -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.`. +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 apps on all platforms. + +## Process command line arguments + +Now, write all arguments on the command line to the output. + +1. Replace the current contents of `AsciiArt.cs` with the following code: + + ```csharp + if (args.Length > 0) + { + string message = string.Join(' ', args); + Console.WriteLine(message); + } + ``` + +1. You can run this version by typing the following command: + + ```dotnetcli + dotnet run AsciiArt.cs -- This is the command line. + ``` + + 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.`. This version demonstrates these new concepts: - 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. -- The `string.Join` method joins multiple strings into a single string, with the specified separator. In this case, the separator is a single space. -- `Console.WriteLine` writes the string to the standard output console, followed by a new line. - -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: - -```csharp -else -{ - while (Console.ReadLine() is string line && line.Length > 0) - { - Console.WriteLine(line); - } -} -``` +- 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. +- writes the string to the standard output console, followed by a new line. -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 ctrl+C.) 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: - -```text -This is the input file -for a file-based program. -It prints the messages -from a file or the -command line. -There are options you -can choose for ASCII -art and colors. -``` +## Handle standard input -Keep the lines short so they format correctly when you add the feature to use ASCII art. Now, run the program again: +That handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments. -```dotnetcli -cat input.txt | dotnet run AsciiArt.cs -``` +1. Add the following `else` clause to the `if` statement you added in the preceding code: -Now your program can accept either command line arguments or standard input. + ```csharp + else + { + while (Console.ReadLine() is string line && line.Length > 0) + { + Console.WriteLine(line); + } + } + ``` -> [!NOTE] -> -> 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. + The preceding code reads the console input until either a blank line or a `null` is read. (The method returns `null` if the input stream is closed by typing ctrl+C.) -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. First, set *execute* permissions on the source file: +1. Test reading standard input by creating a new text file in the same folder. Name the file `input.txt` and add the following lines: -```bash -chmod +x AsciiArt.cs -``` + :::code language="txt" source="./snippets/file-based-programs/input.txt"::: -Then, add a `#!` directive as the first line of the `AsciiArt.cs` file: + Keep the lines short so they format correctly when you add the feature to use ASCII art. -```csharp -#!/usr/local/share/dotnet/dotnet run -``` +1. Run the program again. -The location of `dotnet` can be different on different unix installations. Use the command `whence dotnet` to local the `dotnet` host in your environment. + With bash: -After making these two changes, you can run the program from the command line directly: + ```bash + cat input.txt | dotnet run AsciiArt.cs + ``` -```bash -./AsciiArt.cs -``` + Or, with PowerShell: -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 apps on all platforms. + ```powershell + Get-Content input.txt | dotnet run AsciiArt.cs + ``` -## Add features and NuGet packages to the program +Now your program can accept either command line arguments or standard input. -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. Add the following directive after the `#!` directive in your AsciiArt.cs file: +## Write ASCII Art output -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ColorfulPackage"::: +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. -> [!IMPORTANT] -> -> The version `1.2.15` was the latest version of the `Colorful.Console` package when this tutorial was last updated. Check the package's [NuGet page](https://www.nuget.org/packages/Colorful.Console) for the latest version to ensure you use a package version with the latest security fixes. +1. Add the following directive after the `#!` directive in your AsciiArt.cs file: + + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ColorfulPackage"::: + + > [!IMPORTANT] + > The version `1.2.15` was the latest version of the `Colorful.Console` package when this tutorial was last updated. Check the package's [NuGet page](https://www.nuget.org/packages/Colorful.Console) for the latest version to ensure you use a package version with the latest security fixes. -Next, change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: +1. Change the lines that call `Console.WriteLine` to use the `Colorful.Console.WriteAscii` method instead: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: -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 added support two features: +1. Run the program, and you see ASCII art output instead of echoed text. + +## Process command options + +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: 1. Quote multiple words that should be written on one line: @@ -188,55 +206,62 @@ The `System.CommandLine` library offers several key benefits: - Built-in tab completion capabilities. - Consistent parsing behavior across applications. -To add command line parsing capabilities, first add the `System.CommandLine` package. Add this directive after the existing package directive: +1. Add the `System.CommandLine` package. Add this directive after the existing package directive: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="CommandLinePackage"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="CommandLinePackage"::: -> [!IMPORTANT] -> -> 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. + > [!IMPORTANT] + > 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. Check the package's [NuGet page](https://www.nuget.org/packages/System.CommandLine) for the latest version to ensure you use a package version with the latest security fixes. -Next, add the necessary using statements at the top of your file (after the `#!` and `#:package` directives): +1. Add the necessary using statements at the top of your file (after the `#!` and `#:package` directives): -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Usings"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Usings"::: -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 and objects to represent the command line option and argument: +1. Define the delay option and messages argument. Add the following code to create the `CommandLine.Option` and `CommandLine.Argument` objects to represent the command line option and argument: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="OptionArgument"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="OptionArgument"::: -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: + 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. -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="RootCommand"::: + 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*. -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 object: +1. Create a root command and configure it with the option and argument. Add the argument and option to the root command: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ParseAndValidate"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="RootCommand"::: + +1. 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 object: + + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ParseAndValidate"::: The preceding code validates all command line arguments. If the validation fails, errors are written to the console, and the app exits. ## Use parsed command line results -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 and classes. They must be after all top-level statements and local functions. Add a `record` declaration to store the messages and the delay option value: +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 and classes. They must be after all top-level statements and local functions. -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Record"::: +1. Add a [`record`](../types/records.md) declaration to store the messages and the delay option value: -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: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="Record"::: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ProcessParsedArgs"::: +1. 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: -Next, 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: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="ProcessParsedArgs"::: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: +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: -Finally, replace the `if` clause you wrote earlier with the following code to process the command line arguments and write the output: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: + +1. Replace the `if` clause you wrote earlier with the following code that processes the command line arguments and write the output: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="InvokeCommand"::: +You created a `record` type that provides structure to the parsed command line options and arguments. New local functions create an instance of the record, and use the record to write the ASCII art output. + ## Test the final application Test the application by running several different commands. If you have trouble, here's the finished sample to compare with what you built: -:::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" range="1-88"::: +:::code language="csharp" source="./snippets/file-based-programs/AsciiArt"::: 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. diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt new file mode 100644 index 0000000000000..78bb5e08a7b4c --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt @@ -0,0 +1,67 @@ +#!/usr/local/share/dotnet/dotnet run + +#:package Colorful.Console@1.2.15 +#:package System.CommandLine@2.0.0-beta6 + +using System.CommandLine; +using System.CommandLine.Parsing; + +Option delayOption = new("--delay") +{ + Description = "Delay between lines, specified as milliseconds.", + DefaultValueFactory = parseResult => 100 +}; + +Argument messagesArgument = new("Messages") +{ + Description = "Text to render." +}; + +RootCommand rootCommand = new("Ascii Art file-based program sample"); + +rootCommand.Options.Add(delayOption); +rootCommand.Arguments.Add(messagesArgument); + +ParseResult result = rootCommand.Parse(args); +foreach (ParseError parseError in result.Errors) +{ + Console.Error.WriteLine(parseError.Message); +} +if (result.Errors.Count > 0) +{ + return 1; +} + +var parsedArgs = await ProcessParseResults(result); + +await WriteAsciiArt(parsedArgs); +return 0; + +async Task ProcessParseResults(ParseResult result) +{ + int delay = result.GetValue(delayOption); + List messages = [.. result.GetValue(messagesArgument) ?? Array.Empty()]; + + if (messages.Count == 0) + { + while (Console.ReadLine() is string line && line.Length > 0) + { + // + Colorful.Console.WriteAscii(line); + // + await Task.Delay(delay); + } + } + return new([.. messages], delay); +} + +async Task WriteAsciiArt(AsciiMessageOptions options) +{ + foreach (string message in options.Messages) + { + Colorful.Console.WriteAscii(message); + await Task.Delay(options.Delay); + } +} + +public record AsciiMessageOptions(string[] Messages, int Delay); diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt index 874d5bcd9bbd4..43df299799bc3 100644 --- a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/input.txt @@ -1,8 +1,10 @@ -This is the input file -for a file-based program. -It prints the messages -from a file or the -command line. -There are options you -can choose for ASCII -art and colors. +Hello from ... +dotnet! + +You can create +file-based apps +in .NET 10 and +C# 14 + +Have fun writing +useful utilities \ No newline at end of file From 7077e6092301b72e24ded00096ecfbde2cdbff45 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Thu, 14 Aug 2025 15:51:25 -0400 Subject: [PATCH 12/12] update steps --- .../fundamentals/tutorials/file-based-programs.md | 12 ++++++++---- .../tutorials/snippets/file-based-programs/AsciiArt | 0 2 files changed, 8 insertions(+), 4 deletions(-) mode change 100644 => 100755 docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index 17a97808c30b2..6374c67570c23 100644 --- a/docs/csharp/fundamentals/tutorials/file-based-programs.md +++ b/docs/csharp/fundamentals/tutorials/file-based-programs.md @@ -20,9 +20,13 @@ In this tutorial, you: > [!div class="checklist"] > > * Create a file-based program. -> * Run the program using the .NET CLI and `#!` directives. -> * Add features and NuGet packages to the program. -> * Parse and process command line arguments and standard input. +> * Add Unix shebang (`#!`) support. +> * Read command line arguments. +> * Handle standard input. +> * Write ASCII art output. +> * Process command line arguments. +> * Use parsed command line results. +> * Test the final application. 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. @@ -94,7 +98,7 @@ After making these two changes, you can run the program from the command line di 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 apps on all platforms. -## Process command line arguments +## Read command line arguments Now, write all arguments on the command line to the output. diff --git a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt old mode 100644 new mode 100755