diff --git a/docs/csharp/fundamentals/tutorials/file-based-programs.md b/docs/csharp/fundamentals/tutorials/file-based-programs.md index c6ca05192fe02..582e2f174e5d0 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/14/2025 +ms.date: 12/12/2025 ms.topic: tutorial ai-usage: ai-assisted #customer intent: As a developer, I want to build utilities so that more work is automated. @@ -9,7 +9,7 @@ ai-usage: ai-assisted # Tutorial: Build file-based C# programs -*File-based apps* are programs contained within a single `*.cs` file that are built and run without a corresponding project (`*.csproj`) file. File-based apps are ideal for learning C# because they have less complexity: The entire program is stored in a single file. File-based apps are also useful for building command line utilities. On Unix platforms, file-based apps can be run using `#!` (shebang) [directives](../../language-reference/preprocessor-directives.md). +*File-based apps* are programs contained within a single `*.cs` file that you build and run without a corresponding project (`*.csproj`) file. File-based apps are ideal for learning C# because they have less complexity: The entire program is stored in a single file. File-based apps are also useful for building command line utilities. On Unix platforms, you can run file-based apps by using `#!` (shebang) [directives](../../language-reference/preprocessor-directives.md). In this tutorial, you: > [!div class="checklist"] @@ -23,7 +23,7 @@ In this tutorial, you: > * 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. +You build a file-based program that writes text as ASCII art. The app is contained in a single file, uses NuGet packages, and implements core features. ## Prerequisites @@ -45,15 +45,15 @@ You build a file-based program that writes text as ASCII art. The app is contain 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 first time you run this program, the `dotnet` host builds the executable from your source file, stores build artifacts in a temporary folder, and then runs the created executable. You can verify this experience by typing `dotnet run AsciiArt.cs` again. This time, the `dotnet` host determines that the executable is current and runs the executable without building it again. You don't see any build output. -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): +The preceding steps demonstrate that file-based apps aren't script files. They're C# source files that the `dotnet` host builds by using a generated project file in a temporary folder. One of the lines of output displayed when you build the program should look something like this (on Windows): ```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: +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 @@ -61,15 +61,15 @@ 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 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. +File-based apps are regular C# programs. The only limitation is that you must write them in one source file. You can use top-level statements or a classic `Main` method as an entry point. You can declare any types: classes, interfaces, and structs. You can structure the algorithms in a file-based program the same as you would in any C# program. You can even declare multiple namespaces to organize your code. If you find a file-based program is growing too large for a single file, you can convert it to a project-based program and split the source into multiple files. File-based apps are a great prototyping tool. You can start experimenting with minimal overhead to prove concepts and build algorithms. ## Unix shebang (`#!`) support > [!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's no 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 apps directly, typing the source file name on the command line instead of `dotnet run`. You need to make two changes: +On Unix, you can run file-based apps directly. Instead of using `dotnet run`, you type the source file name on the command line. You need to make two changes: 1. Set *execute* permissions on the source file: @@ -83,7 +83,7 @@ On unix, you can run file-based apps directly, typing the source file name on th #!/usr/local/share/dotnet/dotnet run ``` -The location of `dotnet` can be different on different unix installations. Use the command `which dotnet` to locate the `dotnet` host in your environment. +The location of `dotnet` can be different on different Unix installations. Use the command `which dotnet` to locate the `dotnet` host in your environment. Alternatively, you can use `#!/usr/bin/env dotnet` to resolve the dotnet path from the PATH environment variable automatically: @@ -123,13 +123,13 @@ Now, write all arguments on the command line to the output. 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 predefined variable `args` passes the command line arguments to the program. The `args` variable is an array of strings: `string[]`. If the length of `args` is 0, no arguments were provided. Otherwise, each word on the argument list is stored in the corresponding entry in the array. - 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. ## Handle standard input -That handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments. +The preceding code handles command line arguments correctly. Now, add the code to handle reading input from standard input (`stdin`) instead of command line arguments. 1. Add the following `else` clause to the `if` statement you added in the preceding code: @@ -153,13 +153,13 @@ That handles command line arguments correctly. Now, add the code to handle readi 1. Run the program again. - With bash: + By using bash: ```bash cat input.txt | dotnet run AsciiArt.cs ``` - Or, with PowerShell: + Or, by using PowerShell: ```powershell Get-Content input.txt | dotnet run AsciiArt.cs @@ -167,11 +167,11 @@ That handles command line arguments correctly. Now, add the code to handle readi Now your program can accept either command line arguments or standard input. -## Write ASCII Art output +## Write ASCII art output -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. +Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuget.org/packages/Colorful.Console). To add a package to a file-based program, use the `#:package` directive. -1. Add the following directive after the `#!` directive in your AsciiArt.cs file: +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"::: @@ -182,11 +182,11 @@ Next, add a package that supports ASCII art, [Colorful.Console](https://www.nuge :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: -1. Run the program, and you see ASCII art output instead of echoed text. +1. Run the program. 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: +Next, add command line parsing. The current version writes each word as a different line of output. The command line arguments you add support two features: 1. Quote multiple words that should be written on one line: @@ -200,9 +200,9 @@ Next, let's add command line parsing. The current version writes each word as a AsciiArt.cs --delay 1000 ``` -Users should be able to use both arguments together. +Users can 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. +Most command line applications need to parse command line arguments to handle options, commands, and user input effectively. The [`System.CommandLine` library](../../../standard/commandline/index.md) provides comprehensive capabilities to handle commands, subcommands, options, and arguments. You can concentrate on what your application does rather than the mechanics of parsing command line input. The `System.CommandLine` library offers several key benefits: @@ -238,7 +238,7 @@ The `System.CommandLine` library offers several key benefits: :::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. +The preceding code validates all command line arguments. If the validation fails, the app writes errors to the console and exits. ## Use parsed command line results @@ -254,9 +254,9 @@ Now, finish the app to use the parsed options and write the output. First, defin 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: - :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAscii"::: + :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="WriteAsciiArt"::: -1. Replace the `if` clause you wrote earlier with the following code that processes the command line arguments and write the output: +1. Replace the `if` clause you wrote earlier with the following code that processes the command line arguments and writes the output: :::code language="csharp" source="./snippets/file-based-programs/AsciiArt.cs" id="InvokeCommand"::: @@ -268,10 +268,11 @@ Test the application by running several different commands. If you have trouble, :::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. +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 +- [File-based apps reference](../../../core/sdk/file-based-apps.md) - [Top level statement](../program-structure/top-level-statements.md) - [Preprocessor directives](../../language-reference/preprocessor-directives.md#file-based-apps) - [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 index 761dafc2ebb9a..8b23a043b7165 100755 --- a/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs +++ b/docs/csharp/fundamentals/tutorials/snippets/file-based-programs/AsciiArt.cs @@ -1,4 +1,4 @@ -#!/usr/local/share/dotnet/dotnet run +#!/usr/bin/env dotnet run // #:package Colorful.Console@1.2.15 @@ -71,7 +71,8 @@ async Task ProcessParseResults(ParseResult result) } // -// + +// async Task WriteAsciiArt(AsciiMessageOptions options) { foreach (string message in options.Messages) @@ -80,7 +81,7 @@ async Task WriteAsciiArt(AsciiMessageOptions options) await Task.Delay(options.Delay); } } -// +// // public record AsciiMessageOptions(string[] Messages, int Delay); diff --git a/docs/csharp/language-reference/preprocessor-directives.md b/docs/csharp/language-reference/preprocessor-directives.md index 45227fac3a27b..307e0110639a7 100644 --- a/docs/csharp/language-reference/preprocessor-directives.md +++ b/docs/csharp/language-reference/preprocessor-directives.md @@ -21,9 +21,7 @@ f1_keywords: - "#pragma checksum" - "defaultline_CSharpKeyword" - "#!" - - "#:sdk" - - "#:property" - - "#:package" + - "#:" helpviewer_keywords: - "preprocessor directives [C#]" - "keywords [C#], preprocessor directives" @@ -62,69 +60,9 @@ Console.WriteLine("Hello"); The preceding code snippet informs a Unix shell to execute the file using `dotnet run`. The `/usr/bin/env` command locates the `dotnet` executable in your PATH, making this approach portable across different Unix and macOS distributions. The `#!` line must be the first line in the file, and the following tokens are the program to run. You need to enable the *execute* (`x`) permission on the C# file for that feature. -The `#:` directives that are used in file-based apps include: - -- `#:sdk`: - - The first instance specifies the value for the `` node. Subsequent instances specify the `` node. The version can be omitted (i.e. if specified in global.json or included in .NET SDK). For example: - - ```csharp - #:sdk Microsoft.NET.Sdk.Web - #:sdk Aspire.AppHost.Sdk@9.4.1 - ``` - - The two preceding preprocessors is translated into: - - ```xml - - - ``` - -- `#:property`: - - Instances of `#:property` are translated into property elements in a ``. A token of the form `Name=value` must follow the `property` token. The following example directives are valid `property` tokens: - - ```csharp - #:property TargetFramework=net11.0 - #:property LangVersion=preview - ``` - - The preceding two properties are translated into: - - ```xml - net11.0 - preview - ``` - -- `#:package`: - - Instances of `#:package` are translated into `PackageReference` elements to include NuGet packages with the specified version to your file. For example: - - ```csharp - #:package System.CommandLine@2.0.0-* - ``` - - The preceding preprocessor token is translated into: - - ```xml - - ``` - -- `#:project`: - - Instances of `#:project` are translated into `ProjectReference` elements to include the project with the specified path to the project. For example: - - ```csharp - #:project ../Path/To.Example - ``` - - The preceding preprocessor token is translated into: - - ```xml - - ``` +The `#:` directives that are used in file-based apps are described in the [file based apps reference](../../core/sdk/file-based-apps.md). -Tools can add new tokens following the `#:` convention. +Other tools can add new tokens following the `#:` convention. ## Nullable context