diff --git a/.github/prompts/error-consolidation.md b/.github/prompts/error-consolidation.md index 1660f67f1bd7e..7d7b4ea99d553 100644 --- a/.github/prompts/error-consolidation.md +++ b/.github/prompts/error-consolidation.md @@ -1,46 +1,60 @@ # Copilot prompts to consolidate error codes. -We're going to edit this file, string-literal.md, to contain information about all errors and warnings related to string and character literal declarations. I'll write prompts for specific tasks. Don't make any edits yet. In future prompts, the destination for new error and warning content is always this file. +Overall steps: +1. Make a new template, by hand. +1. Add any new errors that should be added here. +1. Consolidate existing errors, as identified by person. +1. Run Copilot search for other existing errors that person may have missed. +1. Search for missing errors. ## Add a single existing file into the new consolidated article. -Start with CS1009.md as the source file. -For each source file: +We're going to work through a series of files consolidating errors and warnings related to declaring overloaded operators. + +The destination for all these edits is the overloaded-operator-errors.md file. It already contains a skeleton for the final output. + +For each source file I specify in this chat, you'll do the following tasks: - Add the contents of the source file to the destination.md file. - - Include the source error code in the YML header for `f1_keywords` and `helpviewer_keywords`. + - Include the source error code in the YML header for f1_keywords and helpviewer_keywords. - Add an entry with an anchor for the error error code and its corresponding error message. - Add the contents of the source file as a new H2 in the destination file. -- Add a redirection for the source file in the file `.openpublishing.redirection.csharp.json`. Make the destination point to destination file. Place the new entry in sorted order based on 'source_path_from_root'. + - Add a redirection for the source file in the file .openpublishing.redirection.csharp.json. Make the destination point to destination file. Place the new entry in sorted order based on 'source_path_from_root'. - Update the TOC file: - Add the error code to the list of display names in the TOC for the destination file, sorted by numeric error code. - Remove the TOC entry for the source file. -- Finally, delete the source markdown file. + - Finally, delete the source markdown file. ## Search for other related articles that may be missed. -Search all files in the docs/csharp/language-reference/compiler-messages and the docs/csharp/misc folder for any other errors and warnings that involve string literals or constants. Give me a list to review for possible additional consolidation. +Search all files in the docs/csharp/language-reference/compiler-messages and the docs/csharp/misc folder for any other errors and warnings that involve operator overloading. Give me a list to review for possible additional consolidation. Don't make any edits until the originating user approves. ## Final search in roslyn source +Let's check undocumented errors and the roslyn source for any missing errors. For every error code listed in "sorry-we-don-t-have-specifics-on-this-csharp-error.md" under the `f1_keywords` front matter, do the following: +1. Find that number as a constant in `ErrorCodes.cs`. +2. Locate the corresponding `data` element in CSharpResources.resx. The `name` atttribute should match the number of the constant. +3. Read the error message found in the `` element that is a child of that `` element. +Give me a list of all error numbers and corresponding error messages that relate to operator overloading. + To make sure you've found all related errors, we'll check the source. Look in `CSharpResources.resx` for any elements where the `` element is a message related to preprocessor tokens. The symbolic constant for that value is in the `name` attribute on the parent `data` element. Find that value in `ErrorCodes.cs`. It will map to the compiler error code, where the code is "CS" followed by the number as a four digit number. Build a list of any related errors, but don't make any edits yet. -For each new file: +I'll give you error codes one by one. For each, I want you to do the following: -- Add the new error code to the front matter of the consolidated article, for both the `f1_keywords` and `helpview_keywords` table. -- Add the new error code and error message to the table at the top of the destination article. -- Add the new error code to the list of `displaName` elements in the TOC file. +- Add the new error code to the front matter of operator-overloading-errors.md, for both the `f1_keywords` and `helpview_keywords` table. +- Add the new error code and error message to the table at the top of operator-overloading-errors.md. +- Add the new error code to the list of `displayName` elements in the TOC file for operator-overloading-errors.md. - Remove the new error code from the front matter in the file `csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-errors.md` file. Note that no redirections need to be added for these error codes. ## Build consolidated sections -For all remaining work, all edits will be in the `string-literal.md` file. The final format should mirror the structure of the `preprocessor-errors.md` file. Every H2 is a theme, all anchors are for the theme, not an individual error code. +For all remaining work, all edits will be in the `overloaded-operator-errors.md` file. The final format should mirror the structure of the `preprocessor-errors.md` file. Every H2 is a theme, all anchors are for the theme, not an individual error code. To do that, make a new H2 section for the theme. Remove all the H2s for the individual error codes that are part of that theme. Where applicable, the new H2 can include text or examples from the H2s you remove. The new section should include links to language reference articles that discuss the feature or theme. The list of errors at the top of the file should remain in numerical order, so it's easy for readers to scan. Each impacted error code should now have a link to the anchor tag for the new section. Repeat the list in the new section, but without the anchors, as shown in the highlighted text. -Start with the section for all errors related to raw string literals. +Understand these instructions, then suggest a list of themes and the included error codes. I'll approve each theme before you begin editing. diff --git a/.openpublishing.redirection.csharp.json b/.openpublishing.redirection.csharp.json index a33f7459c71e8..2ef465f8e10f9 100644 --- a/.openpublishing.redirection.csharp.json +++ b/.openpublishing.redirection.csharp.json @@ -359,6 +359,14 @@ "source_path_from_root": "/docs/csharp/language-reference/compiler-messages/cs0270.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/array-declaration-errors" }, + { + "source_path_from_root": "/docs/csharp/language-reference/compiler-messages/cs0552.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/language-reference/compiler-messages/cs0563.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, { "source_path_from_root": "/docs/csharp/language-reference/compiler-messages/cs0650.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/array-declaration-errors" @@ -1549,6 +1557,14 @@ "source_path_from_root": "/docs/csharp/misc/cs0035.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/array-declaration-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0056.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0057.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0105.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/using-directive-errors" @@ -1589,6 +1605,22 @@ "source_path_from_root": "/docs/csharp/misc/cs0206.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/ref-modifiers-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0215.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0216.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0217.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0218.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0225.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/params-arrays" @@ -1649,6 +1681,10 @@ "source_path_from_root": "/docs/csharp/misc/cs0440.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/using-directive-errors" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0448.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0457.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overload-resolution" @@ -1685,6 +1721,74 @@ "source_path_from_root": "/docs/csharp/misc/cs0526.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/constructor-errors#constructor-declarations" }, + { + "source_path_from_root": "/docs/csharp/misc/cs0553.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0554.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0555.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0556.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0557.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0558.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0559.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0562.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0564.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0567.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0590.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0660.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0661.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs0715.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs1037.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs1553.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, + { + "source_path_from_root": "/docs/csharp/misc/cs1554.md", + "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/overloaded-operator-errors" + }, { "source_path_from_root": "/docs/csharp/misc/cs0568.md", "redirect_url": "/dotnet/csharp/language-reference/compiler-messages/constructor-errors#constructors-in-struct-types" diff --git a/docs/core/extensions/logging.md b/docs/core/extensions/logging.md index 3a7bfd0d64f5b..341231b29e79d 100644 --- a/docs/core/extensions/logging.md +++ b/docs/core/extensions/logging.md @@ -603,7 +603,7 @@ The following code enables scopes for the console provider: HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Logging.ClearProviders(); -builder.Logging.AddConsole(options => options.IncludeScopes = true); +builder.Logging.AddSimpleConsole(options => options.IncludeScopes = true); using IHost host = builder.Build(); diff --git a/docs/core/whats-new/dotnet-10/libraries.md b/docs/core/whats-new/dotnet-10/libraries.md index 7a9a2425ca3d0..f0e3bf8597835 100644 --- a/docs/core/whats-new/dotnet-10/libraries.md +++ b/docs/core/whats-new/dotnet-10/libraries.md @@ -2,14 +2,14 @@ title: What's new in .NET libraries for .NET 10 description: Learn about the updates to the .NET libraries for .NET 10. titleSuffix: "" -ms.date: 09/09/2025 +ms.date: 10/15/2025 ai-usage: ai-assisted ms.update-cycle: 3650-days --- # What's new in .NET libraries for .NET 10 -This article describes new features in the .NET libraries for .NET 10. It's been updated for RC 1. +This article describes new features in the .NET libraries for .NET 10. It's been updated for RC 2. ## Cryptography diff --git a/docs/core/whats-new/dotnet-10/overview.md b/docs/core/whats-new/dotnet-10/overview.md index f10a0d5596b7c..1ffca77d92077 100644 --- a/docs/core/whats-new/dotnet-10/overview.md +++ b/docs/core/whats-new/dotnet-10/overview.md @@ -2,14 +2,14 @@ title: What's new in .NET 10 description: Learn about the new features introduced in .NET 10 for the runtime, libraries, and SDK. Also find links to what's new in other areas, such as ASP.NET Core. titleSuffix: "" -ms.date: 09/09/2025 +ms.date: 10/15/2025 ai-usage: ai-assisted ms.update-cycle: 3650-days --- # What's new in .NET 10 -Learn about the new features in .NET 10 and find links to further documentation. This page has been updated for RC 1. +Learn about the new features in .NET 10 and find links to further documentation. This page has been updated for RC 2. .NET 10, the successor to [.NET 9](../dotnet-9/overview.md), is [supported for three years](https://dotnet.microsoft.com/platform/support/policy/dotnet-core) as a long-term support (LTS) release. You can [download .NET 10 here](https://get.dot.net/10). diff --git a/docs/core/whats-new/dotnet-10/runtime.md b/docs/core/whats-new/dotnet-10/runtime.md index e447b046a2357..5fd1f337fe0e5 100644 --- a/docs/core/whats-new/dotnet-10/runtime.md +++ b/docs/core/whats-new/dotnet-10/runtime.md @@ -2,13 +2,13 @@ title: What's new in .NET 10 runtime description: Learn about the new features introduced in the .NET 10 runtime. titleSuffix: "" -ms.date: 09/09/2025 +ms.date: 10/15/2025 ai-usage: ai-assisted ms.update-cycle: 3650-days --- # What's new in the .NET 10 runtime -This article describes new features and performance improvements in the .NET runtime for .NET 10. It's been updated for RC 1. +This article describes new features and performance improvements in the .NET runtime for .NET 10. It's been updated for RC 2. ## JIT compiler improvements diff --git a/docs/core/whats-new/dotnet-10/sdk.md b/docs/core/whats-new/dotnet-10/sdk.md index 9e28c2e120bce..7a05a272f7915 100644 --- a/docs/core/whats-new/dotnet-10/sdk.md +++ b/docs/core/whats-new/dotnet-10/sdk.md @@ -2,14 +2,14 @@ title: What's new in the SDK and tooling for .NET 10 description: Learn about the new .NET SDK features introduced in .NET 10. titleSuffix: "" -ms.date: 09/09/2025 +ms.date: 10/15/2025 ai-usage: ai-assisted ms.update-cycle: 3650-days --- # What's new in the SDK and tooling for .NET 10 -This article describes new features and enhancements in the .NET SDK for .NET 10. It's been updated for RC 1. +This article describes new features and enhancements in the .NET SDK for .NET 10. It's been updated for RC 2. ## .NET tools enhancements @@ -111,6 +111,55 @@ The output provides a structured, machine-readable description of the command's } ``` +## Use .NET MSBuild tasks with .NET Framework MSBuild + +MSBuild is the underlying build system for .NET, driving both build of projects (as seen in commands like `dotnet build` and `dotnet pack`), and acting as a general provider of information about projects (as seen in commands like `dotnet list package`, and implicitly used by commands like `dotnet run` to discover how a project wants to be executed). + +When running `dotnet` CLI commands, the version of MSBuild that is used is the one that is shipped with the .NET SDK. However, when using Visual Studio or invoking MSBuild directly, the version of MSBuild that is used is the one that is installed with Visual Studio. This environment difference has a few important consequences. The most important is that MSBuild running in Visual Studio (or through `msbuild.exe`) is a .NET Framework application, while MSBuild running in the `dotnet` CLI is a .NET application. This means that any MSBuild tasks that are written to run on .NET can't be used when building in Visual Studio or when using `msbuild.exe`. + +Starting with .NET 10, `msbuild.exe` and Visual Studio 2026 can run MSBuild tasks that are built for .NET. This means that you can now use the same MSBuild tasks when building in Visual Studio or using `msbuild.exe` as you do when building with the `dotnet` CLI. For most .NET users, this won't change anything. But for authors of custom MSBuild tasks, this means that you can now write your tasks to target .NET and have them work everywhere. The goal with this change is to make it easier to write and share MSBuild tasks, and to allow task authors to take advantage of the latest features in .NET. In addition, this change reduces the difficulties around multi-targeting tasks to support both .NET Framework and .NET, and dealing with versions of .NET Framework dependencies that are implicitly available in the MSBuild .NET Framework execution space. + +### Configure .NET tasks + +For task authors, it's easy to opt into this new behavior. Just change your `UsingTask` declaration to tell MSBuild about your task. + +```xml + +``` + +The `Runtime="NET"` and `TaskFactory="TaskHostFactory"` attributes tell the MSBuild engine how to run the Task: + +- `Runtime="NET"` tells MSBuild that the Task is built for .NET (as opposed to .NET Framework). +- `TaskFactory="TaskHostFactory"` tells MSBuild to use the `TaskHostFactory` to run the Task, which is an existing capability of MSBuild that allows tasks to be run out-of-process. + +### Caveats and performance tuning + +The preceding example is the simplest way to get started using .NET tasks in MSBuild, but it has some limitations. Because the `TaskHostFactory` always runs tasks out-of-process, the new .NET task always runs in a separate process from MSBuild. This means that there is some minor overhead to running the task because the MSBuild engine and the task communicate over inter-process communication (IPC) instead of in-process communication. For most tasks, this overhead is negligible, but for tasks that are run many times in a build, or that do a lot of logging, this overhead might be more significant. + +With just a bit more work, you can configure the task to still run in-process when running via `dotnet`: + +```xml + + +``` + +Thanks to the `Condition` feature of MSBuild, you can load a Task differently depending on whether MSBuild is running in .NET Framework (Visual Studio or `msbuild.exe`) or .NET (the `dotnet` CLI). In this example, the Task runs out-of-process when running in Visual Studio or `msbuild.exe`, but runs in-process when running in the `dotnet` CLI. This gives the best performance when running in the `dotnet` CLI, while still allowing the Task to be used in Visual Studio and `msbuild.exe`. + +There are also small technical limitations to be aware of when using .NET Tasks in MSBuild—the most notable of which is that the `Host Object` feature of MSBuild Tasks isn't yet supported for .NET Tasks running out-of-process. This means that if your Task relies on a Host Object, it won't work when running in Visual Studio or `msbuild.exe`. Additional support for Host Objects is planned in future releases. + ## 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-apps) and [Building and running C# programs](../../../csharp/fundamentals/program-structure/index.md#building-and-running-c-programs). diff --git a/docs/csharp/advanced-topics/expression-trees/index.md b/docs/csharp/advanced-topics/expression-trees/index.md index 35a0cac9c0521..b9e0348d98697 100644 --- a/docs/csharp/advanced-topics/expression-trees/index.md +++ b/docs/csharp/advanced-topics/expression-trees/index.md @@ -1,7 +1,7 @@ --- title: "Expression Trees" description: Learn about expression trees. See how to compile and run code represented by these data structures, where each node is an expression. -ms.date: 05/29/2024 +ms.date: 10/13/2025 ms.custom: updateeachrelease --- # Expression Trees @@ -42,7 +42,7 @@ The C# compiler generates expression trees only from expression lambdas (or sing There are some newer C# language elements that don't translate well into expression trees. Expression trees can't contain `await` expressions, or `async` lambda expressions. Many of the features added in C# 6 and later don't appear exactly as written in expression trees. Instead, newer features are exposed in expression trees in the equivalent, earlier syntax, where possible. Other constructs aren't available. It means that code that interprets expression trees works the same when new language features are introduced. However, even with these limitations, expression trees do enable you to create dynamic algorithms that rely on interpreting and modifying code that is represented as a data structure. It enables rich libraries such as Entity Framework to accomplish what they do. -Expression trees won't support new expression node types. It would be a breaking change for all libraries interpreting expression trees to introduce new node types. The following list includes most C# language elements that can't be used: +Expression trees don't support new expression node types. It would be a breaking change for all libraries interpreting expression trees to introduce new node types. The following list includes most C# language elements that can't be used: - [Conditional methods](../../language-reference/preprocessor-directives.md#conditional-compilation) removed from the output - [`base` access](../../language-reference/keywords/base.md) diff --git a/docs/csharp/asynchronous-programming/task-asynchronous-programming-model.md b/docs/csharp/asynchronous-programming/task-asynchronous-programming-model.md index f405a684252c6..c1632661d7be7 100644 --- a/docs/csharp/asynchronous-programming/task-asynchronous-programming-model.md +++ b/docs/csharp/asynchronous-programming/task-asynchronous-programming-model.md @@ -1,18 +1,18 @@ --- title: The Task Asynchronous Programming (TAP) model with async and await" description: Learn when and how to use Task-based async programming, a simplified approach to asynchronous programming in C#. -ms.date: 02/08/2023 +ms.date: 10/13/2025 --- # Task asynchronous programming model You can avoid performance bottlenecks and enhance the overall responsiveness of your application by using asynchronous programming. However, traditional techniques for writing asynchronous applications can be complicated, making them difficult to write, debug, and maintain. -C# supports simplified approach, async programming, that leverages asynchronous support in the .NET runtime. The compiler does the difficult work that the developer used to do, and your application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort. +C# supports simplified approach, async programming, that uses asynchronous support in the .NET runtime. The compiler does the difficult work that the developer used to do, and your application retains a logical structure that resembles synchronous code. As a result, you get all the advantages of asynchronous programming with a fraction of the effort. -This topic provides an overview of when and how to use async programming and includes links to support topics that contain details and examples. +This article provides an overview of when and how to use async programming and includes links to other articles that contain details and examples. -## Async improves responsiveness +## Async improves responsiveness Asynchrony is essential for activities that are potentially blocking, such as web access. Access to a web resource sometimes is slow or delayed. If such an activity is blocked in a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn't depend on the web resource until the potentially blocking task finishes. @@ -25,13 +25,13 @@ The following table shows typical areas where asynchronous programming improves | Working with images | |

| | WCF programming | [Synchronous and Asynchronous Operations](../../framework/wcf/synchronous-and-asynchronous-operations.md) | | -Asynchrony proves especially valuable for applications that access the UI thread because all UI-related activity usually shares one thread. If any process is blocked in a synchronous application, all are blocked. Your application stops responding, and you might conclude that it has failed when instead it's just waiting. +Asynchrony proves especially valuable for applications that access the UI thread because all UI-related activity usually shares one thread. If any process is blocked in a synchronous application, all are blocked. Your application stops responding, and you might conclude that it failed when instead it's just waiting. When you use asynchronous methods, the application continues to respond to the UI. You can resize or minimize a window, for example, or you can close the application if you don't want to wait for it to finish. The async-based approach adds the equivalent of an automatic transmission to the list of options that you can choose from when designing asynchronous operations. That is, you get all the benefits of traditional asynchronous programming but with much less effort from the developer. -## Async methods are easy to write +## Async methods are easy to write The [async](../language-reference/keywords/async.md) and [await](../language-reference/operators/await.md) keywords in C# are the heart of async programming. By using those two keywords, you can use resources in .NET Framework, .NET Core, or the Windows Runtime to create an asynchronous method almost as easily as you create a synchronous method. Asynchronous methods that you define by using the `async` keyword are referred to as *async methods*. @@ -41,7 +41,7 @@ You can find a complete Windows Presentation Foundation (WPF) example available :::code language="csharp" source="snippets/access-web/Program.cs" id="ControlFlow"::: -You can learn several practices from the preceding sample. Start with the method signature. It includes the `async` modifier. The return type is `Task` (See "Return Types" section for more options). The method name ends in `Async`. In the body of the method, `GetStringAsync` returns a `Task`. That means that when you `await` the task you'll get a `string` (`contents`). Before awaiting the task, you can do work that doesn't rely on the `string` from `GetStringAsync`. +You can learn several practices from the preceding sample. Start with the method signature. It includes the `async` modifier. The return type is `Task` (See "Return Types" section for more options). The method name ends in `Async`. In the body of the method, `GetStringAsync` returns a `Task`. That means that when you `await` the task you get a `string` (`contents`). Before awaiting the task, you can do work that doesn't rely on the `string` from `GetStringAsync`. Pay close attention to the `await` operator. It suspends `GetUrlContentLengthAsync`: @@ -69,15 +69,15 @@ The following characteristics summarize what makes the previous example an async - `void` if you're writing an async event handler. - Any other type that has a `GetAwaiter` method. - For more information, see the [Return types and parameters](#BKMK_ReturnTypesandParameters) section. + For more information, see the [Return types and parameters](#return-types-and-parameters) section. -- The method usually includes at least one `await` expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspended, and control returns to the method's caller. The next section of this topic illustrates what happens at the suspension point. +- The method usually includes at least one `await` expression, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspended, and control returns to the method's caller. The next section of this article illustrates what happens at the suspension point. In async methods, you use the provided keywords and types to indicate what you want to do, and the compiler does the rest, including keeping track of what must happen when control returns to an await point in a suspended method. Some routine processes, such as loops and exception handling, can be difficult to handle in traditional asynchronous code. In an async method, you write these elements much as you would in a synchronous solution, and the problem is solved. For more information about asynchrony in previous versions of .NET Framework, see [TPL and traditional .NET Framework asynchronous programming](../../standard/parallel-programming/tpl-and-traditional-async-programming.md). -## What happens in an async method +## What happens in an async method The most important thing to understand in asynchronous programming is how the control flow moves from method to method. The following diagram leads you through the process: @@ -93,39 +93,39 @@ The numbers in the diagram correspond to the following steps, initiated when a c `GetStringAsync` returns a , where `TResult` is a string, and `GetUrlContentLengthAsync` assigns the task to the `getStringTask` variable. The task represents the ongoing process for the call to `GetStringAsync`, with a commitment to produce an actual string value when the work is complete. -1. Because `getStringTask` hasn't been awaited yet, `GetUrlContentLengthAsync` can continue with other work that doesn't depend on the final result from `GetStringAsync`. That work is represented by a call to the synchronous method `DoIndependentWork`. +1. Because `getStringTask` isn't awaited yet, `GetUrlContentLengthAsync` can continue with other work that doesn't depend on the final result from `GetStringAsync`. That work is represented by a call to the synchronous method `DoIndependentWork`. 1. `DoIndependentWork` is a synchronous method that does its work and returns to its caller. -1. `GetUrlContentLengthAsync` has run out of work that it can do without a result from `getStringTask`. `GetUrlContentLengthAsync` next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string. +1. `GetUrlContentLengthAsync` runs out of work that it can do without a result from `getStringTask`. `GetUrlContentLengthAsync` next wants to calculate and return the length of the downloaded string, but the method can't calculate that value until the method has the string. Therefore, `GetUrlContentLengthAsync` uses an await operator to suspend its progress and to yield control to the method that called `GetUrlContentLengthAsync`. `GetUrlContentLengthAsync` returns a `Task` to the caller. The task represents a promise to produce an integer result that's the length of the downloaded string. > [!NOTE] - > If `GetStringAsync` (and therefore `getStringTask`) completes before `GetUrlContentLengthAsync` awaits it, control remains in `GetUrlContentLengthAsync`. The expense of suspending and then returning to `GetUrlContentLengthAsync` would be wasted if the called asynchronous process `getStringTask` has already completed and `GetUrlContentLengthAsync` doesn't have to wait for the final result. + > If `GetStringAsync` (and therefore `getStringTask`) completes before `GetUrlContentLengthAsync` awaits it, control remains in `GetUrlContentLengthAsync`. The expense of suspending and then returning to `GetUrlContentLengthAsync` would be wasted if the called asynchronous process `getStringTask` is complete and `GetUrlContentLengthAsync` doesn't have to wait for the final result. Inside the calling method the processing pattern continues. The caller might do other work that doesn't depend on the result from `GetUrlContentLengthAsync` before awaiting that result, or the caller might await immediately. The calling method is waiting for `GetUrlContentLengthAsync`, and `GetUrlContentLengthAsync` is waiting for `GetStringAsync`. 1. `GetStringAsync` completes and produces a string result. The string result isn't returned by the call to `GetStringAsync` in the way that you might expect. (Remember that the method already returned a task in step 3.) Instead, the string result is stored in the task that represents the completion of the method, `getStringTask`. The await operator retrieves the result from `getStringTask`. The assignment statement assigns the retrieved result to `contents`. -1. When `GetUrlContentLengthAsync` has the string result, the method can calculate the length of the string. Then the work of `GetUrlContentLengthAsync` is also complete, and the waiting event handler can resume. In the full example at the end of the topic, you can confirm that the event handler retrieves and prints the value of the length result. -If you are new to asynchronous programming, take a minute to consider the difference between synchronous and asynchronous behavior. A synchronous method returns when its work is complete (step 5), but an async method returns a task value when its work is suspended (steps 3 and 6). When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task. +1. When `GetUrlContentLengthAsync` has the string result, the method can calculate the length of the string. Then the work of `GetUrlContentLengthAsync` is also complete, and the waiting event handler can resume. In the full example at the end of the article, you can confirm that the event handler retrieves and prints the value of the length result. +If you're new to asynchronous programming, take a minute to consider the difference between synchronous and asynchronous behavior. A synchronous method returns when its work is complete (step 5), but an async method returns a task value when its work is suspended (steps 3 and 6). When the async method eventually completes its work, the task is marked as completed and the result, if any, is stored in the task. -## API async methods +## API async methods -You might be wondering where to find methods such as `GetStringAsync` that support async programming. .NET Framework 4.5 or higher and .NET Core contain many members that work with `async` and `await`. You can recognize them by the "Async" suffix that's appended to the member name, and by their return type of or . For example, the `System.IO.Stream` class contains methods such as , , and alongside the synchronous methods , , and . +You might be wondering where to find methods such as `GetStringAsync` that support async programming. .NET Framework 4.5 or higher and .NET Core contain many members that work with `async` and `await`. You can recognize them by the "Async" suffix appended to the member name, and by their return type of or . For example, the `System.IO.Stream` class contains methods such as , , and alongside the synchronous methods , , and . The Windows Runtime also contains many methods that you can use with `async` and `await` in Windows apps. For more information, see [Threading and async programming](/windows/uwp/threading-async/) for UWP development, and [Asynchronous programming (Windows Store apps)](/previous-versions/windows/apps/hh464924(v=win.10)) and [Quickstart: Calling asynchronous APIs in C# or Visual Basic](/previous-versions/windows/apps/hh452713(v=win.10)) if you use earlier versions of the Windows Runtime. -## Threads +## Threads Async methods are intended to be non-blocking operations. An `await` expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. -The `async` and `await` keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available. +The `async` and `await` keywords don't cause extra threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available. The async-based approach to asynchronous programming is preferable to existing approaches in almost every case. In particular, this approach is better than the class for I/O-bound operations because the code is simpler and you don't have to guard against race conditions. In combination with the method, async programming is better than for CPU-bound operations because async programming separates the coordination details of running your code from the work that `Task.Run` transfers to the thread pool. -## async and await +## Async and await If you specify that a method is an async method by using the [async](../language-reference/keywords/async.md) modifier, you enable the following two capabilities. @@ -137,12 +137,12 @@ If you specify that a method is an async method by using the [async](../language An async method typically contains one or more occurrences of an `await` operator, but the absence of `await` expressions doesn't cause a compiler error. If an async method doesn't use an `await` operator to mark a suspension point, the method executes as a synchronous method does, despite the `async` modifier. The compiler issues a warning for such methods. -`async` and `await` are contextual keywords. For more information and examples, see the following topics: +`async` and `await` are contextual keywords. For more information and examples, see the following articles: - [async](../language-reference/keywords/async.md) - [await](../language-reference/operators/await.md) -## Return types and parameters +## Return types and parameters An async method typically returns a or a . Inside an async method, an `await` operator is applied to a task that's returned from a call to another async method. @@ -150,7 +150,7 @@ You specify as the return type if the met You use as the return type if the method has no return statement or has a return statement that doesn't return an operand. -You can also specify any other return type, provided that the type includes a `GetAwaiter` method. is an example of such a type. It is available in the [System.Threading.Tasks.Extension](https://www.nuget.org/packages/System.Threading.Tasks.Extensions/) NuGet package. +You can also specify any other return type, if the type includes a `GetAwaiter` method. is an example of such a type. It's available in the [System.Threading.Tasks.Extension](https://www.nuget.org/packages/System.Threading.Tasks.Extensions/) NuGet package. The following example shows how you declare and call a method that returns a or a : @@ -198,13 +198,13 @@ Asynchronous APIs in Windows Runtime programming have one of the following retur - - -## Naming convention +## Naming convention -By convention, methods that return commonly awaitable types (for example, `Task`, `Task`, `ValueTask`, `ValueTask`) should have names that end with "Async". Methods that start an asynchronous operation but do not return an awaitable type should not have names that end with "Async", but may start with "Begin", "Start", or some other verb to suggest this method does not return or throw the result of the operation. +By convention, methods that return commonly awaitable types (for example, `Task`, `Task`, `ValueTask`, `ValueTask`) should have names that end with "Async". Methods that start an asynchronous operation but don't return an awaitable type shouldn't have names that end with "Async", but might start with "Begin", "Start", or some other verb to suggest this method doesn't return or throw the result of the operation. You can ignore the convention where an event, base class, or interface contract suggests a different name. For example, you shouldn't rename common event handlers, such as `OnButtonClick`. -## Related articles (Visual Studio) +## Related articles (Visual Studio) | Title | Description | |--|--| @@ -212,8 +212,8 @@ You can ignore the convention where an event, base class, or interface contract | [Async return types (C#)](async-return-types.md) | Illustrates the types that async methods can return, and explains when each type is appropriate. | | Cancel tasks with a cancellation token as a signaling mechanism. | Shows how to add the following functionality to your async solution:

- [Cancel a list of tasks (C#)](cancel-an-async-task-or-a-list-of-tasks.md)
- [Cancel tasks after a period of time (C#)](cancel-async-tasks-after-a-period-of-time.md)
- [Process asynchronous task as they complete (C#)](start-multiple-async-tasks-and-process-them-as-they-complete.md) | | [Using async for file access (C#)](using-async-for-file-access.md) | Lists and demonstrates the benefits of using async and await to access files. | -| [Task-based asynchronous pattern (TAP)](../../standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md) | Describes an asynchronous pattern, the pattern is based on the and types. | -| [Async Videos on Channel 9](/shows/browse?terms=async) | Provides links to a variety of videos about async programming. | +| [Task-based asynchronous pattern (TAP)](../../standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap.md) | Describes an asynchronous pattern. The pattern is based on the and types. | +| [Async Videos on Channel 9](/shows/browse?terms=async) | Provides links to various videos about async programming. | ## See also diff --git a/docs/csharp/fundamentals/coding-style/identifier-names.md b/docs/csharp/fundamentals/coding-style/identifier-names.md index e93bc1ded9ff9..b5dd421dad868 100644 --- a/docs/csharp/fundamentals/coding-style/identifier-names.md +++ b/docs/csharp/fundamentals/coding-style/identifier-names.md @@ -1,13 +1,15 @@ --- title: "Identifier names - rules and conventions" description: "Learn the rules for valid identifier names in the C# programming language. In addition, learn the common naming conventions used by the .NET runtime team and the .NET docs team." -ms.date: 11/27/2023 +ms.date: 10/10/2025 ai-usage: ai-assisted --- # C# identifier naming rules and conventions An **identifier** is the name you assign to a type (class, interface, struct, delegate, or enum), member, variable, or namespace. +This article covers the essential rules for valid C# identifiers and the naming conventions used to help you write consistent, professional code. + ## Naming rules Valid identifiers must follow these rules. The C# compiler produces an error for any identifier that doesn't follow these rules: @@ -184,15 +186,15 @@ The following guidelines apply to type parameters on generic type parameters. Ty - **Do** name generic type parameters with descriptive names, unless a single letter name is completely self explanatory and a descriptive name wouldn't add value. - :::code language="./snippets/coding-conventions" source="./snippets/coding-conventions/Program.cs" id="TypeParametersOne"::: + :::code language="csharp" source="./snippets/coding-conventions/Program.cs" id="TypeParametersOne"::: - **Consider** using `T` as the type parameter name for types with one single letter type parameter. - :::code language="./snippets/coding-conventions" source="./snippets/coding-conventions/Program.cs" id="TypeParametersTwo"::: + :::code language="csharp" source="./snippets/coding-conventions/Program.cs" id="TypeParametersTwo"::: - **Do** prefix descriptive type parameter names with "T". - :::code language="./snippets/coding-conventions" source="./snippets/coding-conventions/Program.cs" id="TypeParametersThree"::: + :::code language="csharp" source="./snippets/coding-conventions/Program.cs" id="TypeParametersThree"::: - **Consider** indicating constraints placed on a type parameter in the name of parameter. For example, a parameter constrained to `ISession` might be called `TSession`. diff --git a/docs/csharp/fundamentals/object-oriented/inheritance.md b/docs/csharp/fundamentals/object-oriented/inheritance.md index 89e8dce56f25f..f6b833e6ce4b1 100644 --- a/docs/csharp/fundamentals/object-oriented/inheritance.md +++ b/docs/csharp/fundamentals/object-oriented/inheritance.md @@ -16,15 +16,15 @@ ms.assetid: 81d64ee4-50f9-4d6c-a8dc-257c348d2eea Inheritance, together with encapsulation and polymorphism, is one of the three primary characteristics of object-oriented programming. Inheritance enables you to create new classes that reuse, extend, and modify the behavior defined in other classes. The class whose members are inherited is called the *base class*, and the class that inherits those members is called the *derived class*. A derived class can have only one direct base class. However, inheritance is transitive. If `ClassC` is derived from `ClassB`, and `ClassB` is derived from `ClassA`, `ClassC` inherits the members declared in `ClassB` and `ClassA`. > [!NOTE] -> Structs do not support inheritance, but they can implement interfaces. +> Structs don't support inheritance, but they can implement interfaces. Conceptually, a derived class is a specialization of the base class. For example, if you have a base class `Animal`, you might have one derived class that is named `Mammal` and another derived class that is named `Reptile`. A `Mammal` is an `Animal`, and a `Reptile` is an `Animal`, but each derived class represents different specializations of the base class. -Interface declarations may define a default implementation for its members. These implementations are inherited by derived interfaces, and by classes that implement those interfaces. For more information on default interface methods, see the article on [interfaces](../types/interfaces.md). +Interface declarations can define a default implementation for its members. These implementations are inherited by derived interfaces, and by classes that implement those interfaces. For more information on default interface methods, see the article on [interfaces](../types/interfaces.md). When you define a class to derive from another class, the derived class implicitly gains all the members of the base class, except for its constructors and finalizers. The derived class reuses the code in the base class without having to reimplement it. You can add more members in the derived class. The derived class extends the functionality of the base class. -The following illustration shows a class `WorkItem` that represents an item of work in some business process. Like all classes, it derives from and inherits all its methods. `WorkItem` adds six members of its own. These members include a constructor, because constructors aren't inherited. Class `ChangeRequest` inherits from `WorkItem` and represents a particular kind of work item. `ChangeRequest` adds two more members to the members that it inherits from `WorkItem` and from . It must add its own constructor, and it also adds `originalItemID`. Property `originalItemID` enables the `ChangeRequest` instance to be associated with the original `WorkItem` to which the change request applies. +The following illustration shows a class `WorkItem` that represents an item of work in some business process. Like all classes, it derives from and inherits all its methods. `WorkItem` adds six members of its own. These members include a constructor, because constructors aren't inherited. Class `ChangeRequest` inherits from `WorkItem` and represents a particular type of work item. `ChangeRequest` adds two more members to the members that it inherits from `WorkItem` and from . It must add its own constructor, and it also adds `originalItemID`. Property `originalItemID` enables the `ChangeRequest` instance to be associated with the original `WorkItem` to which the change request applies. ![Diagram that shows class inheritance](./media/inheritance/class-inheritance-diagram.png) @@ -46,7 +46,7 @@ You can declare a class as [abstract](../../language-reference/keywords/abstract ## Interfaces -An *interface* is a reference type that defines a set of members. All classes and structs that implement that interface must implement that set of members. An interface may define a default implementation for any or all of these members. A class can implement multiple interfaces even though it can derive from only a single direct base class. +An *interface* is a reference type that defines a set of members. All classes and structs that implement that interface must implement that set of members. An interface might define a default implementation for any or all of these members. A class can implement multiple interfaces even though it can derive from only a single direct base class. Interfaces are used to define specific capabilities for classes that don't necessarily have an "is a" relationship. For example, the interface can be implemented by any class or struct to determine whether two objects of the type are equivalent (however the type defines equivalence). doesn't imply the same kind of "is a" relationship that exists between a base class and a derived class (for example, a `Mammal` is an `Animal`). For more information, see [Interfaces](../types/interfaces.md). @@ -56,4 +56,4 @@ A class can prevent other classes from inheriting from it, or from any of its me ## Derived class hiding of base class members -A derived class can hide base class members by declaring members with the same name and signature. The [`new`](../../language-reference/keywords/new-modifier.md) modifier can be used to explicitly indicate that the member isn't intended to be an override of the base member. The use of [`new`](../../language-reference/keywords/new-modifier.md) isn't required, but a compiler warning will be generated if [`new`](../../language-reference/keywords/new-modifier.md) isn't used. For more information, see [Versioning with the Override and New Keywords](../../programming-guide/classes-and-structs/versioning-with-the-override-and-new-keywords.md) and [Knowing When to Use Override and New Keywords](../../programming-guide/classes-and-structs//knowing-when-to-use-override-and-new-keywords.md). +A derived class can hide base class members by declaring members with the same name and signature. The [`new`](../../language-reference/keywords/new-modifier.md) modifier can be used to explicitly indicate that the member isn't intended to be an override of the base member. The use of [`new`](../../language-reference/keywords/new-modifier.md) isn't required, but a compiler warning is generated if [`new`](../../language-reference/keywords/new-modifier.md) isn't used. For more information, see [Versioning with the Override and New Keywords](../../programming-guide/classes-and-structs/versioning-with-the-override-and-new-keywords.md) and [Knowing When to Use Override and New Keywords](../../programming-guide/classes-and-structs//knowing-when-to-use-override-and-new-keywords.md). diff --git a/docs/csharp/fundamentals/object-oriented/objects.md b/docs/csharp/fundamentals/object-oriented/objects.md index 9f003e0893984..b53f1a5ec8a18 100644 --- a/docs/csharp/fundamentals/object-oriented/objects.md +++ b/docs/csharp/fundamentals/object-oriented/objects.md @@ -1,14 +1,14 @@ --- title: "Objects - create instances of types" description: C# uses a class or struct definition to define types of objects. In an object-oriented language such as C#, a program consists of objects interacting dynamically. -ms.date: 05/14/2021 +ms.date: 10/13/2025 helpviewer_keywords: - "objects [C#], about objects" - "variables [C#]" --- # Objects - create instances of types -A class or struct definition is like a blueprint that specifies what the type can do. An object is basically a block of memory that has been allocated and configured according to the blueprint. A program may create many objects of the same class. Objects are also called instances, and they can be stored in either a named variable or in an array or collection. Client code is the code that uses these variables to call the methods and access the public properties of the object. In an object-oriented language such as C#, a typical program consists of multiple objects interacting dynamically. +A class or struct definition is like a blueprint that specifies what the type can do. An object is basically a block of memory that is allocated and configured according to the blueprint. A program might create many objects of the same class. Objects are also called instances, and they can be stored in either a named variable or in an array or collection. Client code is the code that uses these variables to call the methods and access the public properties of the object. In an object-oriented language such as C#, a typical program consists of multiple objects interacting dynamically. > [!NOTE] > Static types behave differently than what is described here. For more information, see [Static Classes and Static Class Members](../../programming-guide/classes-and-structs/static-classes-and-static-class-members.md). @@ -25,10 +25,10 @@ Because structs are value types, a variable of a struct object holds a copy of t :::code language="csharp" source="./snippets/objects/Application.cs" interactive="try-dotnet"::: -The memory for both `p1` and `p2` is allocated on the thread stack. That memory is reclaimed along with the type or method in which it's declared. This is one reason why structs are copied on assignment. By contrast, the memory that is allocated for a class instance is automatically reclaimed (garbage collected) by the common language runtime when all references to the object have gone out of scope. It isn't possible to deterministically destroy a class object like you can in C++. For more information about garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md). +The memory for both `p1` and `p2` is allocated on the thread stack. That memory is reclaimed along with the type or method in which it's declared. This is one reason why structs are copied on assignment. By contrast, the memory that is allocated for a class instance is automatically reclaimed (garbage collected) by the common language runtime when all references to the object are out of scope. It isn't possible to deterministically destroy a class object like you can in C++. For more information about garbage collection in .NET, see [Garbage Collection](../../../standard/garbage-collection/index.md). > [!NOTE] -> The allocation and deallocation of memory on the managed heap is highly optimized in the common language runtime. In most cases there is no significant difference in the performance cost of allocating a class instance on the heap versus allocating a struct instance on the stack. +> The allocation and deallocation of memory on the managed heap is highly optimized in the common language runtime. In most cases, there's no significant difference in the performance cost of allocating a class instance on the heap versus allocating a struct instance on the stack. ## Object Identity vs. Value Equality @@ -39,7 +39,7 @@ When you compare two objects for equality, you must first distinguish whether yo :::code language="csharp" source="./snippets/objects/Equality.cs" ID="Snippet32"::: - The default implementation of `Equals` uses boxing and reflection in some cases. For information about how to provide an efficient equality algorithm that is specific to your type, see [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md). Records are reference types that use value semantics for equality. + The default implementation of `Equals` uses boxing and reflection in some cases. For information about how to provide an efficient equality algorithm that's specific to your type, see [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md). Records are reference types that use value semantics for equality. - To determine whether the values of the fields in two class instances are equal, you might be able to use the method or the [== operator](../../language-reference/operators/equality-operators.md#equality-operator-). However, only use them if the class has overridden or overloaded them to provide a custom definition of what "equality" means for objects of that type. The class might also implement the interface or the interface. Both interfaces provide methods that can be used to test value equality. When designing your own classes that override `Equals`, make sure to follow the guidelines stated in [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md) and . diff --git a/docs/csharp/fundamentals/object-oriented/polymorphism.md b/docs/csharp/fundamentals/object-oriented/polymorphism.md index f62506825e912..38b932d808f58 100644 --- a/docs/csharp/fundamentals/object-oriented/polymorphism.md +++ b/docs/csharp/fundamentals/object-oriented/polymorphism.md @@ -1,7 +1,7 @@ --- title: "Polymorphism" description: Learn about polymorphism, a key concept in object-oriented programming languages like C#, which describes the relationship between base and derived classes. -ms.date: 05/14/2021 +ms.date: 10/13/2025 helpviewer_keywords: - "C# language, polymorphism" - "polymorphism [C#]" @@ -10,8 +10,8 @@ helpviewer_keywords: Polymorphism is often referred to as the third pillar of object-oriented programming, after encapsulation and inheritance. Polymorphism is a Greek word that means "many-shaped" and it has two distinct aspects: -- At run time, objects of a derived class may be treated as objects of a base class in places such as method parameters and collections or arrays. When this polymorphism occurs, the object's declared type is no longer identical to its run-time type. -- Base classes may define and implement [virtual](../../language-reference/keywords/virtual.md) *methods*, and derived classes can [override](../../language-reference/keywords/override.md) them, which means they provide their own definition and implementation. At run-time, when client code calls the method, the CLR looks up the run-time type of the object, and invokes that override of the virtual method. In your source code you can call a method on a base class, and cause a derived class's version of the method to be executed. +- At run time, objects of a derived class can be treated as objects of a base class in places such as method parameters and collections or arrays. When this polymorphism occurs, the object's declared type is no longer identical to its run-time type. +- Base classes might define and implement [virtual](../../language-reference/keywords/virtual.md) *methods*, and derived classes can [override](../../language-reference/keywords/override.md) them, which means they provide their own definition and implementation. At run-time, when client code calls the method, the CLR looks up the run-time type of the object, and invokes that override of the virtual method. In your source code you can call a method on a base class, and cause a derived class's version of the method to be executed. Virtual methods enable you to work with groups of related objects in a uniform way. For example, suppose you have a drawing application that enables a user to create various kinds of shapes on a drawing surface. You don't know at compile time which specific types of shapes the user will create. However, the application has to keep track of all the various types of shapes that are created, and it has to update them in response to user mouse actions. You can use polymorphism to solve this problem in two basic steps: @@ -32,11 +32,11 @@ In C#, every type is polymorphic because all types, including user-defined types ### Virtual members -When a derived class inherits from a base class, it includes all the members of the base class. All the behavior declared in the base class is part of the derived class. That enables objects of the derived class to be treated as objects of the base class. Access modifiers (`public`, `protected`, `private` and so on) determine if those members are accessible from the derived class implementation. Virtual methods gives the designer different choices for the behavior of the derived class: +When a derived class inherits from a base class, it includes all the members of the base class. All the behavior declared in the base class is part of the derived class. That enables objects of the derived class to be treated as objects of the base class. Access modifiers (`public`, `protected`, `private`, and so on) determine if those members are accessible from the derived class implementation. Virtual methods give the designer different choices for the behavior of the derived class: -- The derived class may override virtual members in the base class, defining new behavior. -- The derived class may inherit the closest base class method without overriding it, preserving the existing behavior but enabling further derived classes to override the method. -- The derived class may define new non-virtual implementation of those members that hide the base class implementations. +- The derived class can override virtual members in the base class, defining new behavior. +- The derived class can inherit the closest base class method without overriding it, preserving the existing behavior but enabling further derived classes to override the method. +- The derived class can define new non-virtual implementation of those members that hide the base class implementations. A derived class can override a base class member only if the base class member is declared as [virtual](../../language-reference/keywords/virtual.md) or [abstract](../../language-reference/keywords/abstract.md). The derived member must use the [override](../../language-reference/keywords/override.md) keyword to explicitly indicate that the method is intended to participate in virtual invocation. The following code provides an example: @@ -50,7 +50,7 @@ Virtual methods and properties enable derived classes to extend a base class wit ### Hide base class members with new members -If you want your derived class to have a member with the same name as a member in a base class, you can use the [new](../../language-reference/keywords/new-modifier.md) keyword to hide the base class member. The `new` keyword is put before the return type of a class member that is being replaced. The following code provides an example: +If you want your derived class to have a member with the same name as a member in a base class, you can use the [new](../../language-reference/keywords/new-modifier.md) keyword to hide the base class member. The `new` keyword is put before the return type of a class member that's being replaced. The following code provides an example: :::code language="csharp" source="./snippets/inheritance/Inheritance.cs" ID="SnippetNewMethods"::: @@ -64,7 +64,7 @@ In this example, both variables refer to the same object instance, but the metho ### Prevent derived classes from overriding virtual members -Virtual members remain virtual, regardless of how many classes have been declared between the virtual member and the class that originally declared it. If class `A` declares a virtual member, and class `B` derives from `A`, and class `C` derives from `B`, class `C` inherits the virtual member, and may override it, regardless of whether class `B` declared an override for that member. The following code provides an example: +Virtual members remain virtual, regardless of how many classes are declared between the virtual member and the class that originally declared it. If class `A` declares a virtual member, and class `B` derives from `A`, and class `C` derives from `B`, class `C` inherits the virtual member, and might override it, regardless of whether class `B` declared an override for that member. The following code provides an example: :::code language="csharp" source="./snippets/inheritance/Hierarchy.cs" ID="SnippetFirstHierarchy"::: @@ -76,11 +76,11 @@ In the previous example, the method `DoWork` is no longer virtual to any class d :::code language="csharp" source="./snippets/inheritance/Hierarchy.cs" ID="SnippetNewDeclaration"::: -In this case, if `DoWork` is called on `D` using a variable of type `D`, the new `DoWork` is called. If a variable of type `C`, `B`, or `A` is used to access an instance of `D`, a call to `DoWork` will follow the rules of virtual inheritance, routing those calls to the implementation of `DoWork` on class `C`. +In this case, if `DoWork` is called on `D` using a variable of type `D`, the new `DoWork` is called. If a variable of type `C`, `B`, or `A` is used to access an instance of `D`, a call to `DoWork` follows the rules of virtual inheritance, routing those calls to the implementation of `DoWork` on class `C`. ### Access base class virtual members from derived classes -A derived class that has replaced or overridden a method or property can still access the method or property on the base class using the `base` keyword. The following code provides an example: +A derived class that replaces or overrides a method or property can still access the method or property on the base class using the `base` keyword. The following code provides an example: ```csharp public class Base @@ -102,4 +102,4 @@ public class Derived : Base For more information, see [base](../../language-reference/keywords/base.md). > [!NOTE] -> It is recommended that virtual members use `base` to call the base class implementation of that member in their own implementation. Letting the base class behavior occur enables the derived class to concentrate on implementing behavior specific to the derived class. If the base class implementation is not called, it is up to the derived class to make their behavior compatible with the behavior of the base class. +> It's recommended that virtual members use `base` to call the base class implementation of that member in their own implementation. Letting the base class behavior occur enables the derived class to concentrate on implementing behavior specific to the derived class. If the base class implementation isn't called, it's up to the derived class to make their behavior compatible with the behavior of the base class. diff --git a/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Inheritance.cs b/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Inheritance.cs index 1e1e08970ef4d..67ebbde974977 100644 --- a/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Inheritance.cs +++ b/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Inheritance.cs @@ -28,10 +28,10 @@ public WorkItem() // Instance constructor that has three parameters. public WorkItem(string title, string desc, TimeSpan joblen) { - this.ID = GetNextID(); - this.Title = title; - this.Description = desc; - this.jobLength = joblen; + ID = GetNextID(); + Title = title; + Description = desc; + jobLength = joblen; } // Static constructor to initialize the static member, currentID. This @@ -93,10 +93,10 @@ public ChangeRequest(string title, string desc, TimeSpan jobLen, public class Shape { // A few example members - public int X { get; private set; } - public int Y { get; private set; } - public int Height { get; set; } - public int Width { get; set; } + public int X { get; init; } + public int Y { get; init; } + public int Height { get; init; } + public int Width { get; init; } // Virtual method public virtual void Draw() @@ -143,12 +143,12 @@ public static void Example() // can all be used wherever a Shape is expected. No cast is // required because an implicit conversion exists from a derived // class to its base class. - var shapes = new List - { + List shapes = + [ new Rectangle(), new Triangle(), new Circle() - }; + ]; // Polymorphism at work #2: the virtual method Draw is // invoked on each of the derived classes, not the base class. @@ -170,7 +170,7 @@ Performing base class drawing tasks public static void VirtualExamples() { // - DerivedClass B = new DerivedClass(); + DerivedClass B = new(); B.DoWork(); // Calls the new method. BaseClass A = B; @@ -183,10 +183,7 @@ public static void VirtualExamples() public class BaseClass { public virtual void DoWork() { } - public virtual int WorkProperty - { - get { return 0; } - } + public virtual int WorkProperty => 0; } public class DerivedClass : BaseClass { @@ -227,7 +224,7 @@ public static class NewMethods public static void Example() { // - DerivedClass B = new DerivedClass(); + DerivedClass B = new(); B.DoWork(); // Calls the new method. BaseClass A = (BaseClass)B; diff --git a/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Program.cs b/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Program.cs index 42790a2a18a6b..043b887af6009 100644 --- a/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Program.cs +++ b/docs/csharp/fundamentals/object-oriented/snippets/inheritance/Program.cs @@ -7,16 +7,16 @@ static void Main(string[] args) // // Create an instance of WorkItem by using the constructor in the // base class that takes three arguments. - WorkItem item = new WorkItem("Fix Bugs", - "Fix all bugs in my code branch", - new TimeSpan(3, 4, 0, 0)); + WorkItem item = new("Fix Bugs", + "Fix all bugs in my code branch", + new TimeSpan(3, 4, 0, 0)); // Create an instance of ChangeRequest by using the constructor in // the derived class that takes four arguments. - ChangeRequest change = new ChangeRequest("Change Base Class Design", - "Add members to the class", - new TimeSpan(4, 0, 0), - 1); + ChangeRequest change = new("Change Base Class Design", + "Add members to the class", + new TimeSpan(4, 0, 0), + 1); // Use the ToString method defined in WorkItem. Console.WriteLine(item.ToString()); diff --git a/docs/csharp/fundamentals/object-oriented/snippets/objects/Application.cs b/docs/csharp/fundamentals/object-oriented/snippets/objects/Application.cs index 35382d189fc94..f92d48c2a7363 100644 --- a/docs/csharp/fundamentals/object-oriented/snippets/objects/Application.cs +++ b/docs/csharp/fundamentals/object-oriented/snippets/objects/Application.cs @@ -19,7 +19,7 @@ static void Main() { // Create struct instance and initialize by using "new". // Memory is allocated on thread stack. - Person p1 = new Person("Alex", 9); + Person p1 = new("Alex", 9); Console.WriteLine($"p1 Name = {p1.Name} Age = {p1.Age}"); // Create new struct object. Note that struct can be initialized diff --git a/docs/csharp/fundamentals/object-oriented/snippets/objects/Equality.cs b/docs/csharp/fundamentals/object-oriented/snippets/objects/Equality.cs index d11e663baa495..de3be1e869706 100644 --- a/docs/csharp/fundamentals/object-oriented/snippets/objects/Equality.cs +++ b/docs/csharp/fundamentals/object-oriented/snippets/objects/Equality.cs @@ -7,19 +7,14 @@ public static void EqualityTest() // // Person is defined in the previous example. - //public struct Person + //public struct Person(string name, int age) //{ - // public string Name; - // public int Age; - // public Person(string name, int age) - // { - // Name = name; - // Age = age; - // } + // public string Name { get; set; } = name; + // public int Age { get; set; } = age; //} - Person p1 = new Person("Wallace", 75); - Person p2 = new Person("", 42); + Person p1 = new("Wallace", 75); + Person p2 = new("", 42); p2.Name = "Wallace"; p2.Age = 75; diff --git a/docs/csharp/fundamentals/object-oriented/snippets/objects/Program.cs b/docs/csharp/fundamentals/object-oriented/snippets/objects/Program.cs index 3853079e5da10..cfba6e9dc4ece 100644 --- a/docs/csharp/fundamentals/object-oriented/snippets/objects/Program.cs +++ b/docs/csharp/fundamentals/object-oriented/snippets/objects/Program.cs @@ -1,14 +1,9 @@ using System; -public class Person +public class Person(string name, int age) { - public string Name { get; set; } - public int Age { get; set; } - public Person(string name, int age) - { - Name = name; - Age = age; - } + public string Name { get; set; } = name; + public int Age { get; set; } = age; // Other properties, methods, events... } @@ -16,7 +11,7 @@ class Program { static void Main() { - Person person1 = new Person("Leopold", 6); + Person person1 = new("Leopold", 6); Console.WriteLine($"person1 Name = {person1.Name} Age = {person1.Age}"); // Declare new person, assign person1 to it. @@ -28,11 +23,12 @@ static void Main() Console.WriteLine($"person2 Name = {person2.Name} Age = {person2.Age}"); Console.WriteLine($"person1 Name = {person1.Name} Age = {person1.Age}"); + + /* + Output: + person1 Name = Leopold Age = 6 + person2 Name = Molly Age = 16 + person1 Name = Molly Age = 16 + */ } } -/* - Output: - person1 Name = Leopold Age = 6 - person2 Name = Molly Age = 16 - person1 Name = Molly Age = 16 -*/ diff --git a/docs/csharp/fundamentals/tutorials/classes.md b/docs/csharp/fundamentals/tutorials/classes.md index 5ae6454e5bdef..7d56e1df8794b 100644 --- a/docs/csharp/fundamentals/tutorials/classes.md +++ b/docs/csharp/fundamentals/tutorials/classes.md @@ -1,11 +1,11 @@ --- title: Classes and objects tutorial description: Create your first C# program and explore object oriented concepts -ms.date: 02/25/2022 +ms.date: 10/10/2025 --- # Explore object oriented programming with classes and objects -In this tutorial, you'll build a console application and see the basic object-oriented features that are part of the C# language. +In this tutorial, you build a console application and see the basic object-oriented features that are part of the C# language. ## Prerequisites @@ -13,7 +13,7 @@ In this tutorial, you'll build a console application and see the basic object-or ## Create your application -Using a terminal window, create a directory named *Classes*. You'll build your application there. Change to that directory and type `dotnet new console` in the console window. This command creates your application. Open *Program.cs*. It should look like this: +Using a terminal window, create a directory named *Classes*. You build your application there. Change to that directory and type `dotnet new console` in the console window. This command creates your application. Open *Program.cs*. It should look like this: ```csharp // See https://aka.ms/new-console-template for more information @@ -22,7 +22,7 @@ Console.WriteLine("Hello, World!"); In this tutorial, you're going to create new types that represent a bank account. Typically developers define each class in a different text file. That makes it easier to manage as a program grows in size. Create a new file named *BankAccount.cs* in the *Classes* directory. -This file will contain the definition of a ***bank account***. Object Oriented programming organizes code by creating types in the form of ***classes***. These classes contain the code that represents a specific entity. The `BankAccount` class represents a bank account. The code implements specific operations through methods and properties. In this tutorial, the bank account supports this behavior: +This file contains the definition of a ***bank account***. Object Oriented programming organizes code by creating types in the form of ***classes***. These classes contain the code that represents a specific entity. The `BankAccount` class represents a bank account. The code implements specific operations through methods and properties. In this tutorial, the bank account supports this behavior: 1. It has a 10-digit number that uniquely identifies the bank account. 1. It has a string that stores the name or names of the owners. @@ -55,7 +55,7 @@ public class BankAccount } ``` -Before going on, let's take a look at what you've built. The `namespace` declaration provides a way to logically organize your code. This tutorial is relatively small, so you'll put all the code in one namespace. +Before going on, let's take a look at what you built. The `namespace` declaration provides a way to logically organize your code. This tutorial is relatively small, so you put all the code in one namespace. `public class BankAccount` defines the class, or type, you're creating. Everything inside the `{` and `}` that follows the class declaration defines the state and behavior of the class. There are five ***members*** of the `BankAccount` class. The first three are ***properties***. Properties are data elements and can have code that enforces validation or other rules. The last two are ***methods***. Methods are blocks of code that perform a single function. Reading the names of each of the members should provide enough information for you or another developer to understand what the class does. @@ -63,7 +63,7 @@ Before going on, let's take a look at what you've built. The `namespace` declar The first feature to implement is to open a bank account. When a customer opens an account, they must supply an initial balance, and information about the owner or owners of that account. -Creating a new object of the `BankAccount` type means defining a ***constructor*** that assigns those values. A ***constructor*** is a member that has the same name as the class. It's used to initialize objects of that class type. Add the following constructor to the `BankAccount` type. Place the following code above the declaration of `MakeDeposit`: +Creating a new object of the `BankAccount` type requires defining a ***constructor*** that assigns those values. A ***constructor*** is a member that has the same name as the class. It's used to initialize objects of that class type. Add the following constructor to the `BankAccount` type. Place the following code above the declaration of `MakeDeposit`: ```csharp public BankAccount(string name, decimal initialBalance) @@ -73,7 +73,7 @@ public BankAccount(string name, decimal initialBalance) } ``` -The preceding code identifies the properties of the object being constructed by including the `this` qualifier. That qualifier is usually optional and omitted. You could also have written: +The preceding code identifies the properties of the object being constructed by including the `this` qualifier. That qualifier is usually optional and omitted. You could also write: ```csharp public BankAccount(string name, decimal initialBalance) @@ -94,9 +94,9 @@ var account = new BankAccount("", 1000); Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} initial balance."); ``` -Let's run what you've built so far. If you're using Visual Studio, Select **Start without debugging** from the **Debug** menu. If you're using a command line, type `dotnet run` in the directory where you've created your project. +Let's run what you built so far. If you're using Visual Studio, Select **Start without debugging** from the **Debug** menu. If you're using a command line, type `dotnet run` in the directory where you created your project. -Did you notice that the account number is blank? It's time to fix that. The account number should be assigned when the object is constructed. But it shouldn't be the responsibility of the caller to create it. The `BankAccount` class code should know how to assign new account numbers. A simple way is to start with a 10-digit number. Increment it when each new account is created. Finally, store the current account number when an object is constructed. +Did you notice that the account number is blank? It's time to fix that. The account number should be assigned when the object is constructed. But it shouldn't be the responsibility of the caller to create it. The `BankAccount` class code should know how to assign new account numbers. A simple way is to start with a 10-digit number. Increment it when each new account is created. Finally, store the current account number when an object is constructed. Add a member declaration to the `BankAccount` class. Place the following line of code after the opening brace `{` at the beginning of the `BankAccount` class: @@ -104,7 +104,7 @@ Add a member declaration to the `BankAccount` class. Place the following line of private static int s_accountNumberSeed = 1234567890; ``` -The `accountNumberSeed` is a data member. It's `private`, which means it can only be accessed by code inside the `BankAccount` class. It's a way of separating the public responsibilities (like having an account number) from the private implementation (how account numbers are generated). It's also `static`, which means it's shared by all of the `BankAccount` objects. The value of a non-static variable is unique to each instance of the `BankAccount` object. The `accountNumberSeed` is a `private static` field and thus has the `s_` prefix as per C# naming conventions. The `s` denoting `static` and `_` denoting `private` field. Add the following two lines to the constructor to assign the account number. Place them after the line that says `this.Balance = initialBalance`: +The `accountNumberSeed` is a data member. It's `private`, which means it can only be accessed by code inside the `BankAccount` class. It's a way of separating the public responsibilities (like having an account number) from the private implementation (how account numbers are generated). It's also `static`, which means all `BankAccount` objects share the same single instance of this variable. The value of a non-static variable is unique to each instance of the `BankAccount` object. The `accountNumberSeed` is a `private static` field and thus has the `s_` prefix as per C# naming conventions. The `s` denoting `static` and `_` denoting `private` field. To initialize each account number, add the following two lines to the constructor. Place them after the line that says `this.Balance = initialBalance`: ```csharp Number = s_accountNumberSeed.ToString(); @@ -117,7 +117,7 @@ Type `dotnet run` to see the results. Your bank account class needs to accept deposits and withdrawals to work correctly. Let's implement deposits and withdrawals by creating a journal of every transaction for the account. Tracking every transaction has a few advantages over simply updating the balance on each transaction. The history can be used to audit all transactions and manage daily balances. Computing the balance from the history of all transactions when needed ensures any errors in a single transaction that are fixed will be correctly reflected in the balance on the next computation. -Let's start by creating a new type to represent a transaction. The transaction is a simple type that doesn't have any responsibilities. It needs a few properties. Create a new file named *Transaction.cs*. Add the following code to it: +Start by creating a new type that represents a transaction. The transaction is a `record` type that doesn't have any responsibilities. It needs a few properties. Create a new file named *Transaction.cs*. Add the following code to it: :::code language="csharp" source="./snippets/introduction-to-classes/Transaction.cs"::: @@ -125,19 +125,19 @@ Now, let's add a of `Transaction` obj :::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="TransactionDeclaration"::: -Now, let's correctly compute the `Balance`. The current balance can be found by summing the values of all transactions. As the code is currently, you can only get the initial balance of the account, so you'll have to update the `Balance` property. Replace the line `public decimal Balance { get; }` in *BankAccount.cs* with the following code: +Now, let's correctly compute the `Balance`. The current balance can be found by summing the values of all transactions. As the code is currently, you can only get the initial balance of the account, so you have to update the `Balance` property. Replace the line `public decimal Balance { get; }` in *BankAccount.cs* with the following code: :::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="BalanceComputation"::: This example shows an important aspect of ***properties***. You're now computing the balance when another programmer asks for the value. Your computation enumerates all transactions, and provides the sum as the current balance. -Next, implement the `MakeDeposit` and `MakeWithdrawal` methods. These methods will enforce the final two rules: the initial balance must be positive, and any withdrawal must not create a negative balance. +Next, implement the `MakeDeposit` and `MakeWithdrawal` methods. These methods enforce the final two rules: the initial balance must be positive, and any withdrawal must not create a negative balance. These rules introduce the concept of ***exceptions***. The standard way of indicating that a method can't complete its work successfully is to throw an exception. The type of exception and the message associated with it describe the error. Here, the `MakeDeposit` method throws an exception if the amount of the deposit isn't greater than 0. The `MakeWithdrawal` method throws an exception if the withdrawal amount isn't greater than 0, or if applying the withdrawal results in a negative balance. Add the following code after the declaration of the `_allTransactions` list: :::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="DepositAndWithdrawal"::: -The [`throw` statement](../../language-reference/statements/exception-handling-statements.md#the-throw-statement) **throws** an exception. Execution of the current block ends, and control transfers to the first matching `catch` block found in the call stack. You'll add a `catch` block to test this code a little later on. +The [`throw` statement](../../language-reference/statements/exception-handling-statements.md#the-throw-statement) **throws** an exception. Execution of the current block ends, and control transfers to the first matching `catch` block found in the call stack. You add a `catch` block to test this code a little later on. The constructor should get one change so that it adds an initial transaction, rather than updating the balance directly. Since you already wrote the `MakeDeposit` method, call it from your constructor. The finished constructor should look like this: @@ -169,7 +169,7 @@ catch (ArgumentOutOfRangeException e) } ``` -You use the [`try-catch` statement](../../language-reference/statements/exception-handling-statements.md#the-try-catch-statement) to mark a block of code that may throw exceptions and to catch those errors that you expect. You can use the same technique to test the code that throws an exception for a negative balance. Add the following code before the declaration of `invalidAccount` in your `Main` method: +You use the [`try-catch` statement](../../language-reference/statements/exception-handling-statements.md#the-try-catch-statement) to mark a block of code that might throw exceptions and to catch those errors that you expect. You can use the same technique to test the code that throws an exception for a negative balance. Add the following code before the declaration of `invalidAccount` in your `Main` method: ```csharp // Test for a negative balance. @@ -192,7 +192,7 @@ To finish this tutorial, you can write the `GetAccountHistory` method that creat :::code language="csharp" source="./snippets/introduction-to-classes/BankAccount.cs" id="History"::: -The history uses the class to format a string that contains one line for each transaction. You've seen the string formatting code earlier in these tutorials. One new character is `\t`. That inserts a tab to format the output. +The history uses the class to format a string that contains one line for each transaction. You used string formatting earlier in these tutorials. One new character is `\t`. That inserts a tab to format the output. Add this line to test it in *Program.cs*: @@ -200,7 +200,7 @@ Add this line to test it in *Program.cs*: Console.WriteLine(account.GetAccountHistory()); ``` -Run your program to see the results. +Run your program and check the results. ## Next steps diff --git a/docs/csharp/fundamentals/tutorials/oop.md b/docs/csharp/fundamentals/tutorials/oop.md index ac8f3a331a3d7..acf3d0e5397ab 100644 --- a/docs/csharp/fundamentals/tutorials/oop.md +++ b/docs/csharp/fundamentals/tutorials/oop.md @@ -1,7 +1,7 @@ --- title: "Object-Oriented Programming" description: C# provides full support for object-oriented programming including abstraction, encapsulation, inheritance, and polymorphism. -ms.date: 02/25/2022 +ms.date: 10/10/2025 --- # Object-Oriented programming (C#) @@ -14,15 +14,15 @@ C# is an object-oriented programming language. The four basic principles of obje In the preceding tutorial, [introduction to classes](classes.md) you saw both *abstraction* and *encapsulation*. The `BankAccount` class provided an abstraction for the concept of a bank account. You could modify its implementation without affecting any of the code that used the `BankAccount` class. Both the `BankAccount` and `Transaction` classes provide encapsulation of the components needed to describe those concepts in code. -In this tutorial, you'll extend that application to make use of *inheritance* and *polymorphism* to add new features. You'll also add features to the `BankAccount` class, taking advantage of the *abstraction* and *encapsulation* techniques you learned in the preceding tutorial. +In this tutorial, you extend that application to make use of *inheritance* and *polymorphism* to add new features. You also add features to the `BankAccount` class, taking advantage of the *abstraction* and *encapsulation* techniques you learned in the preceding tutorial. ## Create different types of accounts -After building this program, you get requests to add features to it. It works great in the situation where there is only one bank account type. Over time, needs change, and related account types are requested: +After building this program, you get requests to add features to it. It works great in the situation where there's only one bank account type. Over time, needs change, and related account types are requested: - An interest earning account that accrues interest at the end of each month. - A line of credit that can have a negative balance, but when there's a balance, there's an interest charge each month. -- A pre-paid gift card account that starts with a single deposit, and only can be paid off. It can be refilled once at the start of each month. +- A prepaid gift card account that starts with a single deposit, and only can be paid off. It can be refilled once at the start of each month. All of these different accounts are similar to `BankAccount` class defined in the earlier tutorial. You could copy that code, rename the classes, and make modifications. That technique would work in the short term, but it would be more work over time. Any changes would be copied across all the affected classes. @@ -42,28 +42,28 @@ public class GiftCardAccount : BankAccount } ``` -Each of these classes *inherits* the shared behavior from their shared *base class*, the `BankAccount` class. Write the implementations for new and different functionality in each of the *derived classes*. These derived classes already have all the behavior defined in the `BankAccount` class. +Each of these classes *inherits* the shared behavior from their shared *base class*, the `BankAccount` class. Write the implementations for new and different functionality in each of the *derived classes*. These derived classes already have all the behavior defined in the `BankAccount` class. It's a good practice to create each new class in a different source file. In [Visual Studio](https://visualstudio.com), you can right-click on the project, and select *add class* to add a new class in a new file. In [Visual Studio Code](https://code.visualstudio.com), select *File* then *New* to create a new source file. In either tool, name the file to match the class: *InterestEarningAccount.cs*, *LineOfCreditAccount.cs*, and *GiftCardAccount.cs*. -When you create the classes as shown in the preceding sample, you'll find that none of your derived classes compile. A constructor is responsible for initializing an object. A derived class constructor must initialize the derived class, and provide instructions on how to initialize the base class object included in the derived class. The proper initialization normally happens without any extra code. The `BankAccount` class declares one public constructor with the following signature: +When you create the classes as shown in the preceding sample, you find that none of your derived classes compile. A constructor is responsible for initializing an object. A derived class constructor must initialize the derived class, and provide instructions on how to initialize the base class object included in the derived class. The proper initialization normally happens without any extra code. The `BankAccount` class declares one public constructor with the following signature: ```csharp public BankAccount(string name, decimal initialBalance) ``` -The compiler doesn't generate a default constructor when you define a constructor yourself. That means each derived class must explicitly call this constructor. You declare a constructor that can pass arguments to the base class constructor. The following code shows the constructor for the `InterestEarningAccount`: +The compiler doesn't generate a default constructor when you define a constructor yourself. That means each derived class must explicitly call this constructor. You declare a constructor that can pass arguments to the base class constructor. The following code shows the constructor for the `InterestEarningAccount`: :::code language="csharp" source="./snippets/object-oriented-programming/InterestEarningAccount.cs" ID="DerivedConstructor"::: -The parameters to this new constructor match the parameter type and names of the base class constructor. You use the `: base()` syntax to indicate a call to a base class constructor. Some classes define multiple constructors, and this syntax enables you to pick which base class constructor you call. Once you've updated the constructors, you can develop the code for each of the derived classes. The requirements for the new classes can be stated as follows: +The parameters to this new constructor match the parameter type and names of the base class constructor. You use the `: base()` syntax to indicate a call to a base class constructor. Some classes define multiple constructors, and this syntax enables you to pick which base class constructor you call. Once you update the constructors, you can develop the code for each of the derived classes. The requirements for the new classes can be stated as follows: - An interest earning account: - - Will get a credit of 2% of the month-ending-balance. + - Gets a credit of 2% of the month-ending-balance. - A line of credit: - Can have a negative balance, but not be greater in absolute value than the credit limit. - - Will incur an interest charge each month where the end of month balance isn't 0. - - Will incur a fee on each withdrawal that goes over the credit limit. + - Incurs an interest charge each month where the end of month balance isn't 0. + - Incurs a fee on each withdrawal that goes over the credit limit. - A gift card account: - Can be refilled with a specified amount once each month, on the last day of the month. @@ -71,7 +71,7 @@ You can see that all three of these account types have an action that takes plac :::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="DeclareMonthEndTransactions"::: -The preceding code shows how you use the `virtual` keyword to declare a method in the base class that a derived class may provide a different implementation for. A `virtual` method is a method where any derived class may choose to reimplement. The derived classes use the `override` keyword to define the new implementation. Typically you refer to this as "overriding the base class implementation". The `virtual` keyword specifies that derived classes may override the behavior. You can also declare `abstract` methods where derived classes must override the behavior. The base class does not provide an implementation for an `abstract` method. Next, you need to define the implementation for two of the new classes you've created. Start with the `InterestEarningAccount`: +The preceding code shows how you use the `virtual` keyword to declare a method in the base class that a derived class can provide a different implementation for. A `virtual` method is a method where any derived class can choose to reimplement. When a derived class defines a new implementation, it's called *overriding the base class implementation*. The derived classes use the `override` keyword to define the new implementation. The `virtual` keyword specifies that derived classes can override the behavior. You can also declare `abstract` methods where derived classes must override the behavior. The base class doesn't provide an implementation for an `abstract` method. Next, you need to define the implementation for two of the new classes you created. Start with the `InterestEarningAccount`: :::code language="csharp" source="./snippets/object-oriented-programming/InterestEarningAccount.cs" ID="ApplyMonthendInterest"::: @@ -83,11 +83,11 @@ The `GiftCardAccount` class needs two changes to implement its month-end functio :::code language="csharp" source="./snippets/object-oriented-programming/GiftCardAccount.cs" ID="GiftCardAccountConstruction"::: -The constructor provides a default value for the `monthlyDeposit` value so callers can omit a `0` for no monthly deposit. Next, override the `PerformMonthEndTransactions` method to add the monthly deposit, if it was set to a non-zero value in the constructor: +The constructor provides a default value for the `monthlyDeposit` value so callers can omit a `0` for no monthly deposit. Next, override the `PerformMonthEndTransactions` method to add the monthly deposit, if it was set to a nonzero value in the constructor: :::code language="csharp" source="./snippets/object-oriented-programming/GiftCardAccount.cs" ID="AddMonthlyDeposit"::: -The override applies the monthly deposit set in the constructor. Add the following code to the `Main` method to test these changes for the `GiftCardAccount` and the `InterestEarningAccount`: +The override applies the monthly deposit set in the constructor. Add the following code to the `Main` method. This code tests these changes for the `GiftCardAccount` and the `InterestEarningAccount`: :::code language="csharp" source="./snippets/object-oriented-programming/Program.cs" ID="FirstTests"::: @@ -104,7 +104,7 @@ lineOfCredit.PerformMonthEndTransactions(); Console.WriteLine(lineOfCredit.GetAccountHistory()); ``` -When you add the preceding code and run the program, you'll see something like the following error: +Add the preceding code and run the program. The output should be something like the following error: ```console Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must be positive (Parameter 'amount') @@ -115,15 +115,15 @@ Unhandled exception. System.ArgumentOutOfRangeException: Amount of deposit must ``` > [!NOTE] -> The actual output includes the full path to the folder with the project. The folder names were omitted for brevity. Also, depending on your code format, the line numbers may be slightly different. +> The actual output includes the full path to the folder with the project. The folder names were omitted for brevity. Also, depending on your code format, the line numbers might be slightly different. -This code fails because the `BankAccount` assumes that the initial balance must be greater than 0. Another assumption baked into the `BankAccount` class is that the balance can't go negative. Instead, any withdrawal that overdraws the account is rejected. Both of those assumptions need to change. The line of credit account starts at 0, and generally will have a negative balance. Also, if a customer borrows too much money, they incur a fee. The transaction is accepted, it just costs more. The first rule can be implemented by adding an optional argument to the `BankAccount` constructor that specifies the minimum balance. The default is `0`. The second rule requires a mechanism that enables derived classes to modify the default algorithm. In a sense, the base class "asks" the derived type what should happen when there's an overdraft. The default behavior is to reject the transaction by throwing an exception. +This code fails because the `BankAccount` assumes that the initial balance must be greater than 0. Another assumption baked into the `BankAccount` class is that the balance can't go negative. Instead, any withdrawal that overdraws the account is rejected. Both of those assumptions need to change. The line of credit account starts at 0, and generally has a negative balance. Also, if a customer borrows too much money, they incur a fee. The transaction is accepted, it just costs more. The first rule can be implemented by adding an optional argument to the `BankAccount` constructor that specifies the minimum balance. The default is `0`. The second rule requires a mechanism that enables derived classes to modify the default algorithm. In a sense, the base class "asks" the derived type what should happen when there's an overdraft. The default behavior is to reject the transaction by throwing an exception. -Let's start by adding a second constructor that includes an optional `minimumBalance` parameter. This new constructor does all the actions done by the existing constructor. Also, it sets the minimum balance property. You could copy the body of the existing constructor, but that means two locations to change in the future. Instead, you can use *constructor chaining* to have one constructor call another. The following code shows the two constructors and the new additional field: +Let's start by adding a second constructor that includes an optional `minimumBalance` parameter. This new constructor does all the actions done by the existing constructor. Also, it sets the minimum balance property. You could copy the body of the existing constructor, but that means two locations to change in the future. Instead, you can use *constructor chaining* to have one constructor call another. The following code shows the two constructors and the new field: :::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="ConstructorModifications"::: -The preceding code shows two new techniques. First, the `minimumBalance` field is marked as `readonly`. That means the value cannot be changed after the object is constructed. Once a `BankAccount` is created, the `minimumBalance` can't change. Second, the constructor that takes two parameters uses `: this(name, initialBalance, 0) { }` as its implementation. The `: this()` expression calls the other constructor, the one with three parameters. This technique allows you to have a single implementation for initializing an object even though client code can choose one of many constructors. +The preceding code shows two new techniques. First, the `minimumBalance` field is marked as `readonly`. That means the value can't be changed after the object is constructed. Once a `BankAccount` is created, the `minimumBalance` can't change. Second, the constructor that takes two parameters uses `: this(name, initialBalance, 0) { }` as its implementation. The `: this()` expression calls the other constructor, the one with three parameters. This technique allows you to have a single implementation for initializing an object even though client code can choose one of many constructors. This implementation calls `MakeDeposit` only if the initial balance is greater than `0`. That preserves the rule that deposits must be positive, yet lets the credit account open with a `0` balance. @@ -165,7 +165,7 @@ Replace it with the following code: :::code language="csharp" source="./snippets/object-oriented-programming/BankAccount.cs" ID="RefactoredMakeWithdrawal"::: -The added method is `protected`, which means that it can be called only from derived classes. That declaration prevents other clients from calling the method. It's also `virtual` so that derived classes can change the behavior. The return type is a `Transaction?`. The `?` annotation indicates that the method may return `null`. Add the following implementation in the `LineOfCreditAccount` to charge a fee when the withdrawal limit is exceeded: +The added method is `protected`, which means that it can be called only from derived classes. That declaration prevents other clients from calling the method. It's also `virtual` so that derived classes can change the behavior. The return type is a `Transaction?`. The `?` annotation indicates that the method can return `null`. Add the following implementation in the `LineOfCreditAccount`. This code charges a fee when the withdrawal limit is exceeded: :::code language="csharp" source="./snippets/object-oriented-programming/LineOfCreditAccount.cs" ID="AddOverdraftFee"::: @@ -183,5 +183,5 @@ This tutorial demonstrated many of the techniques used in Object-Oriented progra - You used *Abstraction* when you defined classes for each of the different account types. Those classes described the behavior for that type of account. - You used *Encapsulation* when you kept many details `private` in each class. -- You used *Inheritance* when you leveraged the implementation already created in the `BankAccount` class to save code. +- You used *Inheritance* when you used the implementation already created in the `BankAccount` class to save code. - You used *Polymorphism* when you created `virtual` methods that derived classes could override to create specific behavior for that account type. diff --git a/docs/csharp/fundamentals/tutorials/snippets/introduction-to-classes/transaction.cs b/docs/csharp/fundamentals/tutorials/snippets/introduction-to-classes/transaction.cs index 4e37cb5913d64..e4a5265d60091 100644 --- a/docs/csharp/fundamentals/tutorials/snippets/introduction-to-classes/transaction.cs +++ b/docs/csharp/fundamentals/tutorials/snippets/introduction-to-classes/transaction.cs @@ -1,15 +1,3 @@ namespace Classes; -public class Transaction -{ - public decimal Amount { get; } - public DateTime Date { get; } - public string Notes { get; } - - public Transaction(decimal amount, DateTime date, string note) - { - Amount = amount; - Date = date; - Notes = note; - } -} +public record Transaction(decimal Amount, DateTime Date, string Notes); diff --git a/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/BankAccount.cs b/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/BankAccount.cs index 04956ec3c9d33..24a8f72c74ad3 100644 --- a/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/BankAccount.cs +++ b/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/BankAccount.cs @@ -37,14 +37,11 @@ public BankAccount(string name, decimal initialBalance, decimal minimumBalance) } // - private readonly List _allTransactions = new(); + private readonly List _allTransactions = []; public void MakeDeposit(decimal amount, DateTime date, string note) { - if (amount <= 0) - { - throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount); var deposit = new Transaction(amount, date, note); _allTransactions.Add(deposit); } @@ -52,10 +49,7 @@ public void MakeDeposit(decimal amount, DateTime date, string note) // public void MakeWithdrawal(decimal amount, DateTime date, string note) { - if (amount <= 0) - { - throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive"); - } + ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount); Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance); Transaction? withdrawal = new(-amount, date, note); _allTransactions.Add(withdrawal); diff --git a/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/transaction.cs b/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/transaction.cs index 0ae2c35ec037d..9b5bccec4f394 100644 --- a/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/transaction.cs +++ b/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming/transaction.cs @@ -1,15 +1,3 @@ namespace OOProgramming; -public class Transaction -{ - public decimal Amount { get; } - public DateTime Date { get; } - public string Notes { get; } - - public Transaction(decimal amount, DateTime date, string note) - { - Amount = amount; - Date = date; - Notes = note; - } -} +public record Transaction(decimal Amount, DateTime Date, string Notes); diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/BankAccount.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/BankAccount.cs new file mode 100644 index 0000000000000..c41fa0e1ff61a --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/BankAccount.cs @@ -0,0 +1,180 @@ +namespace OOProgramming; + +/// +/// Represents a bank account with basic banking operations including deposits, withdrawals, and transaction history. +/// Supports minimum balance constraints and provides extensible month-end processing capabilities. +/// +public class BankAccount +{ + /// + /// Gets the unique account number for this bank account. + /// + /// A string representation of the account number, generated sequentially. + public string Number { get; } + + /// + /// Gets or sets the name of the account owner. + /// + /// The full name of the person who owns this account. + public string Owner { get; set; } + + /// + /// Gets the current balance of the account by calculating the sum of all transactions. + /// + /// The current account balance as a decimal value. + public decimal Balance => _allTransactions.Sum(i => i.Amount); + + private static int s_accountNumberSeed = 1234567890; + + private readonly decimal _minimumBalance; + + /// + /// Initializes a new instance of the BankAccount class with the specified owner name and initial balance. + /// Uses a default minimum balance of 0. + /// + /// The name of the account owner. + /// The initial deposit amount for the account. + /// + /// This constructor is a convenience overload that calls the main constructor with a minimum balance of 0. + /// If the initial balance is greater than 0, it will be recorded as the first transaction with the note "Initial balance". + /// The account number is automatically generated using a static seed value that increments for each new account. + /// + public BankAccount(string name, decimal initialBalance) : this(name, initialBalance, 0) { } + + /// + /// Initializes a new instance of the BankAccount class with the specified owner name, initial balance, and minimum balance constraint. + /// + /// The name of the account owner. + /// The initial deposit amount for the account. + /// The minimum balance that must be maintained in the account. + /// + /// This is the primary constructor that sets up all account properties. The account number is generated automatically + /// using a static seed value. If an initial balance is provided and is greater than 0, it will be added as the first + /// transaction. The minimum balance constraint will be enforced on all future withdrawal operations through the + /// method. + /// + public BankAccount(string name, decimal initialBalance, decimal minimumBalance) + { + Number = s_accountNumberSeed.ToString(); + s_accountNumberSeed++; + + Owner = name; + _minimumBalance = minimumBalance; + if (initialBalance > 0) + MakeDeposit(initialBalance, DateTime.Now, "Initial balance"); + } + + private readonly List _allTransactions = []; + + /// + /// Makes a deposit to the account by adding a positive transaction. + /// + /// The amount to deposit. Must be positive. + /// The date when the deposit is made. + /// A descriptive note about the deposit transaction. + /// Thrown when the deposit amount is zero or negative. + /// + /// This method creates a new object with the specified amount, date, and note, + /// then adds it to the internal transaction list. The transaction amount must be positive - negative amounts + /// are not allowed for deposits. The account balance is automatically updated through the Balance property + /// which calculates the sum of all transactions. There are no limits or restrictions on deposit amounts. + /// + public void MakeDeposit(decimal amount, DateTime date, string note) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive"); + } + var deposit = new Transaction(amount, date, note); + _allTransactions.Add(deposit); + } + + /// + /// Makes a withdrawal from the account by adding a negative transaction. + /// Checks withdrawal limits and minimum balance constraints before processing. + /// + /// The amount to withdraw. Must be positive. + /// The date when the withdrawal is made. + /// A descriptive note about the withdrawal transaction. + /// Thrown when the withdrawal amount is zero or negative. + /// Thrown when the withdrawal would cause the balance to fall below the minimum balance. + /// + /// This method first validates that the withdrawal amount is positive, then checks if the withdrawal would + /// violate the minimum balance constraint by calling . The withdrawal is + /// recorded as a negative transaction amount. If the withdrawal limit check returns an overdraft transaction + /// (such as a fee), that transaction is also added to the account. The method enforces business rules through + /// the virtual CheckWithdrawalLimit method, allowing derived classes to implement different withdrawal policies. + /// + public void MakeWithdrawal(decimal amount, DateTime date, string note) + { + if (amount <= 0) + { + throw new ArgumentOutOfRangeException(nameof(amount), "Amount of withdrawal must be positive"); + } + Transaction? overdraftTransaction = CheckWithdrawalLimit(Balance - amount < _minimumBalance); + Transaction? withdrawal = new(-amount, date, note); + _allTransactions.Add(withdrawal); + if (overdraftTransaction != null) + _allTransactions.Add(overdraftTransaction); + } + + /// + /// Checks whether a withdrawal would violate the account's minimum balance constraint. + /// This method can be overridden in derived classes to implement different withdrawal limit policies. + /// + /// True if the withdrawal would cause the balance to fall below the minimum balance. + /// A Transaction object representing any overdraft fees or penalties, or null if no additional charges apply. + /// Thrown when the withdrawal would cause an overdraft and the account type doesn't allow it. + protected virtual Transaction? CheckWithdrawalLimit(bool isOverdrawn) + { + if (isOverdrawn) + { + throw new InvalidOperationException("Not sufficient funds for this withdrawal"); + } + else + { + return default; + } + } + + /// + /// Generates a detailed account history report showing all transactions with running balance calculations. + /// + /// A formatted string containing the complete transaction history with dates, amounts, running balances, and notes. + /// + /// This method creates a formatted report that includes a header row followed by all transactions in chronological order. + /// Each row shows the transaction date (in short date format), the transaction amount, the running balance after that + /// transaction, and any notes associated with the transaction. The running balance is calculated by iterating through + /// all transactions and maintaining a cumulative total. The report uses tab characters for column separation and + /// is suitable for display in console applications or simple text outputs. + /// + public string GetAccountHistory() + { + var report = new System.Text.StringBuilder(); + + decimal balance = 0; + report.AppendLine("Date\t\tAmount\tBalance\tNote"); + foreach (var item in _allTransactions) + { + balance += item.Amount; + report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{balance}\t{item.Notes}"); + } + + return report.ToString(); + } + + // Added for OO tutorial: + + /// + /// Performs month-end processing for the account. This virtual method can be overridden in derived classes + /// to implement specific month-end behaviors such as interest calculations, fee assessments, or statement generation. + /// + /// + /// The base implementation of this method does nothing, providing a safe default for basic bank accounts. + /// Derived classes such as savings accounts or checking accounts can override this method to implement + /// account-specific month-end processing. Examples include calculating and applying interest payments, + /// assessing monthly maintenance fees, generating account statements, or performing regulatory compliance checks. + /// This method is typically called by banking systems at the end of each month as part of batch processing operations. + /// + public virtual void PerformMonthEndTransactions() { } +} diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/GiftCardAccount.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/GiftCardAccount.cs new file mode 100644 index 0000000000000..9ec4f25976d95 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/GiftCardAccount.cs @@ -0,0 +1,48 @@ +namespace OOProgramming; + +/// +/// Represents a gift card account that extends with optional recurring monthly deposits. +/// Designed for prepaid gift cards that can optionally receive automatic monthly top-ups. +/// +/// +/// A gift card account is a specialized prepaid account that can be loaded with an initial balance and optionally +/// configured to receive automatic monthly deposits. This account type is ideal for gift cards, allowances, or +/// subscription-based funding scenarios. The account uses the standard minimum balance of $0 from the base +/// class, preventing overdrafts while allowing the balance to reach zero. +/// Monthly deposits, when configured, provide a convenient way to automatically replenish the account balance. +/// +public class GiftCardAccount : BankAccount +{ + private readonly decimal _monthlyDeposit = 0m; + + /// + /// Initializes a new instance of the class with the specified owner name, initial balance, and optional monthly deposit amount. + /// + /// The name of the account owner or gift card recipient. + /// The initial amount loaded onto the gift card. + /// The optional amount to be automatically deposited each month. Defaults to 0 (no monthly deposits). + /// + /// This constructor creates a gift card account with an initial balance and an optional recurring monthly deposit. + /// The monthly deposit parameter allows for automatic top-ups, making this suitable for allowance accounts or + /// subscription-based gift cards. If monthlyDeposit is 0 or not specified, no automatic deposits will occur. + /// The account inherits standard banking functionality from with a minimum balance of $0. + /// + public GiftCardAccount(string name, decimal initialBalance, decimal monthlyDeposit = 0) : base(name, initialBalance) + => _monthlyDeposit = monthlyDeposit; + + /// + /// + /// For gift card accounts, month-end processing includes applying automatic monthly deposits when configured. + /// If a monthly deposit amount was specified during account creation and is greater than zero, the amount is + /// automatically deposited with the note "Add monthly deposit". This feature enables recurring funding scenarios + /// such as monthly allowances or subscription top-ups. If no monthly deposit was configured (_monthlyDeposit is 0), + /// no transactions are added during month-end processing. + /// + public override void PerformMonthEndTransactions() + { + if (_monthlyDeposit != 0) + { + MakeDeposit(_monthlyDeposit, DateTime.Now, "Add monthly deposit"); + } + } +} diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/InterestEarningAccount.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/InterestEarningAccount.cs new file mode 100644 index 0000000000000..15a9f77c52e29 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/InterestEarningAccount.cs @@ -0,0 +1,44 @@ +namespace OOProgramming; + +/// +/// Represents an interest-earning bank account that extends with monthly interest payments. +/// Earns interest on balances above $500 at a rate of 2% annually, applied monthly. +/// +/// +/// An interest-earning account is a specialized savings account that rewards customers for maintaining higher balances. +/// Interest is only earned when the account balance exceeds $500, encouraging customers to maintain substantial deposits. +/// The annual interest rate of 2% is applied monthly to qualifying balances, providing a simple savings incentive. +/// This account type uses the standard minimum balance of $0 from the base class. +/// +public class InterestEarningAccount : BankAccount +{ + /// + /// Initializes a new instance of the class with the specified owner name and initial balance. + /// + /// The name of the account owner. + /// The initial deposit amount for the account. + /// + /// This constructor calls the base constructor with a default minimum balance of $0. + /// Interest earnings will begin in the first month-end processing cycle if the balance exceeds $500. + /// The account uses the same transaction tracking and balance calculation mechanisms as the base account type. + /// + public InterestEarningAccount(string name, decimal initialBalance) : base(name, initialBalance) + { + } + + /// + /// + /// For interest-earning accounts, month-end processing includes calculating and applying interest payments on qualifying balances. + /// Interest is only earned when the account balance exceeds $500. The interest calculation uses an annual rate of 2% (0.02) + /// applied to the full balance. The interest payment is deposited as a regular transaction with the note "apply monthly interest". + /// If the balance is $500 or below, no interest is earned and no transactions are added during month-end processing. + /// + public override void PerformMonthEndTransactions() + { + if (Balance > 500m) + { + decimal interest = Balance * 0.02m; + MakeDeposit(interest, DateTime.Now, "apply monthly interest"); + } + } +} diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/LineOfCreditAccount.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/LineOfCreditAccount.cs new file mode 100644 index 0000000000000..3e6dac527be6e --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/LineOfCreditAccount.cs @@ -0,0 +1,57 @@ +namespace OOProgramming; + +/// +/// Represents a line of credit account that extends with credit limit functionality. +/// Allows negative balances up to a specified credit limit and applies monthly interest charges on outstanding balances. +/// +/// +/// A line of credit account differs from a regular bank account in that it allows the balance to go negative +/// up to a predefined credit limit. When the balance is negative (indicating borrowed money), the account +/// accrues monthly interest charges at a rate of 7% annually. Overdraft fees of $20 are applied when +/// withdrawals exceed the available credit limit. +/// +class LineOfCreditAccount : BankAccount +{ + /// + /// Initializes a new instance of the class with the specified owner name, initial balance, and credit limit. + /// + /// The name of the account owner. + /// The initial deposit amount for the account. + /// The maximum credit limit available for borrowing. This value should be positive and represents the maximum negative balance allowed. + /// + /// The constructor converts the credit limit to a negative minimum balance by passing -creditLimit to the base constructor. + /// This allows the account to have a negative balance up to the specified credit limit. For example, a credit limit of $1000 + /// means the account can have a balance as low as -$1000. + /// + public LineOfCreditAccount(string name, decimal initialBalance, decimal creditLimit) : base(name, initialBalance, -creditLimit) + { + } + + /// + /// + /// For line of credit accounts, month-end processing includes calculating and applying interest charges on negative balances. + /// Interest is calculated at an annual rate of 7% (0.07) applied monthly to the outstanding balance. The interest charge + /// is applied as a withdrawal transaction with the note "Charge monthly interest". If the balance is positive or zero, + /// no interest charges are applied. + /// + public override void PerformMonthEndTransactions() + { + if (Balance < 0) + { + // Negate the balance to get a positive interest charge: + decimal interest = -Balance * 0.07m; + MakeWithdrawal(interest, DateTime.Now, "Charge monthly interest"); + } + } + + /// + /// + /// Line of credit accounts handle overdrafts by applying a fixed $20 overdraft fee when withdrawals exceed the available + /// credit limit. Unlike the base implementation which throws an exception, this method returns a fee transaction that + /// gets added to the account. This allows the withdrawal to proceed while documenting the penalty for exceeding the limit. + /// + protected override Transaction? CheckWithdrawalLimit(bool isOverdrawn) => + isOverdrawn + ? new Transaction(-20, DateTime.Now, "Apply overdraft fee") + : default; +} diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/Program.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/Program.cs new file mode 100644 index 0000000000000..6ecc812eedbcb --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/Program.cs @@ -0,0 +1,65 @@ +using OOProgramming; + +IntroToClasses(); + +// +var giftCard = new GiftCardAccount("gift card", 100, 50); +giftCard.MakeWithdrawal(20, DateTime.Now, "get expensive coffee"); +giftCard.MakeWithdrawal(50, DateTime.Now, "buy groceries"); +giftCard.PerformMonthEndTransactions(); +// can make additional deposits: +giftCard.MakeDeposit(27.50m, DateTime.Now, "add some additional spending money"); +Console.WriteLine(giftCard.GetAccountHistory()); + +var savings = new InterestEarningAccount("savings account", 10000); +savings.MakeDeposit(750, DateTime.Now, "save some money"); +savings.MakeDeposit(1250, DateTime.Now, "Add more savings"); +savings.MakeWithdrawal(250, DateTime.Now, "Needed to pay monthly bills"); +savings.PerformMonthEndTransactions(); +Console.WriteLine(savings.GetAccountHistory()); +// + +// +var lineOfCredit = new LineOfCreditAccount("line of credit", 0, 2000); +// How much is too much to borrow? +lineOfCredit.MakeWithdrawal(1000m, DateTime.Now, "Take out monthly advance"); +lineOfCredit.MakeDeposit(50m, DateTime.Now, "Pay back small amount"); +lineOfCredit.MakeWithdrawal(5000m, DateTime.Now, "Emergency funds for repairs"); +lineOfCredit.MakeDeposit(150m, DateTime.Now, "Partial restoration on repairs"); +lineOfCredit.PerformMonthEndTransactions(); +Console.WriteLine(lineOfCredit.GetAccountHistory()); +// +static void IntroToClasses() +{ + var account = new BankAccount("", 1000); + Console.WriteLine($"Account {account.Number} was created for {account.Owner} with {account.Balance} balance."); + + account.MakeWithdrawal(500, DateTime.Now, "Rent payment"); + Console.WriteLine(account.Balance); + account.MakeDeposit(100, DateTime.Now, "friend paid me back"); + Console.WriteLine(account.Balance); + + Console.WriteLine(account.GetAccountHistory()); + + // Test that the initial balances must be positive: + try + { + var invalidAccount = new BankAccount("invalid", -55); + } + catch (ArgumentOutOfRangeException e) + { + Console.WriteLine("Exception caught creating account with negative balance"); + Console.WriteLine(e.ToString()); + } + + // Test for a negative balance + try + { + account.MakeWithdrawal(750, DateTime.Now, "Attempt to overdraw"); + } + catch (InvalidOperationException e) + { + Console.WriteLine("Exception caught trying to overdraw"); + Console.WriteLine(e.ToString()); + } +} diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/object-oriented-programming.csproj b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/object-oriented-programming.csproj new file mode 100644 index 0000000000000..b8b9658ca3968 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/object-oriented-programming.csproj @@ -0,0 +1,13 @@ + + + + Exe + net10.0 + OOProgramming + oo-programming + enable + enable + True + + + diff --git a/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/transaction.cs b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/transaction.cs new file mode 100644 index 0000000000000..38dc60ba7889c --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/snippets/xml-documentation/transaction.cs @@ -0,0 +1,9 @@ +namespace OOProgramming; + +/// +/// Represents an immutable financial transaction with an amount, date, and descriptive notes. +/// +/// The transaction amount. Positive values represent credits/deposits, negative values represent debits/withdrawals. +/// The date and time when the transaction occurred. +/// Descriptive notes or memo text associated with the transaction. +public record Transaction(decimal Amount, DateTime Date, string Notes); diff --git a/docs/csharp/fundamentals/tutorials/xml-documentation.md b/docs/csharp/fundamentals/tutorials/xml-documentation.md new file mode 100644 index 0000000000000..fed376ec885c0 --- /dev/null +++ b/docs/csharp/fundamentals/tutorials/xml-documentation.md @@ -0,0 +1,111 @@ +--- +title: Generate XML documentation from your source code +description: "Learn to add `///` comments that generate XML documentation directly from your source code. Learn which tags are available and how to add documentation blocks to types and members." +ms.topic: tutorial #Don't change. +ms.date: 10/14/2025 +ai-usage: ai-assisted +#customer intent: As a developer, I want to generate XML documentation comments so that other developers can use my code successfully. +--- +# Tutorial: Create XML documentation + +In this tutorial, you take an existing object‑oriented sample (from the preceding [tutorial](oop.md)) and enhance it with XML documentation comments. XML documentation comments provide helpful IntelliSense tooltips, and can participate in generated API reference docs. You learn which elements deserve comments, how to use core tags like ``, ``, ``, ``, ``, ``, ``, ``, and ``, and how consistent, purposeful commenting improves maintainability, discoverability, and collaboration—without adding noise. By the end, you annotated the public surface of the sample, built the project to emit the XML documentation file, and seen how those comments flow directly into the developer experience and downstream documentation tooling. + +In this tutorial, you: + +> [!div class="checklist"] +> +> * Enable XML documentation output in your C# project. +> * Add and structure XML documentation comments to types and members. +> * Build the project and inspect the generated XML documentation file. + +## Prerequisites + +- The [.NET SDK](https://dot.net) +- Either [Visual Studio](https://visualstudio.com) or [Visual Studio Code](https://visualstudio.com/vscode) with the [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit). + +## Enable XML documentation + +Load the project you built in the preceding [object-oriented tutorial](oop.md). If you prefer to start fresh, clone the sample from the `dotnet/docs` repository under the [`snippets/object-oriented-programming`](https://github.com/dotnet/docs/tree/main/docs/csharp/fundamentals/tutorials/snippets/object-oriented-programming) folder. + +Next, enable XML documentation output so the compiler emits a `.xml` file alongside your assembly. Edit the project file and add (or confirm) the following property inside a `` element: + +```xml +True +``` + +If you're using Visual Studio, you can enable this using the "build" property page. + +Build the project. The compiler produces an XML file that aggregates all the `///` comments from publicly visible types and members. That file feeds IntelliSense tooltips, static analysis tools, and downstream documentation generation systems. + +Build the project now. You see warnings for any public members that are missing `` comments. Treat those warnings as a to-do list that helps you provide complete, intentional documentation. Open the generated XML file (it's next to your build output) and inspect the initial structure. At first, the `` section is empty because you didn't add comments yet: + +```xml + + + + oo-programming + + + + +``` + +With the file in place, start adding targeted XML comments and immediately verify how each one appears in the generated output. + +## Add documentation comments + +You now cycle through the build warnings to add concise, useful documentation to the `BankAccount` type. Each warning pinpoints a public member that lacks a `` (or other required) element. Treat the warning list as a checklist. Avoid adding noise: focus on describing intent, invariants, and important usage constraints—skip restating obvious type names or parameter types. + +1. Build the project again. In Visual Studio or Visual Studio Code, open the Error List / Problems panel and filter for documentation warnings (CS1591). At the command line, run a build and review the warnings emitted to the console. +1. Navigate to the first warning (the `BankAccount` class). On the line above the declaration, type `///`. The editor scaffolds a `` element. Replace the placeholder with a single, action‑focused sentence. The sentence explains the role of the account in the domain. For example, it tracks transactions and enforces a minimum balance. +1. Add `` only if you need to explain behavior. Examples include how minimum balance enforcement works or how account numbers are generated. Keep remarks short. +1. For each property (`Number`, `Owner`, `Balance`), type `///` and write a `` that states what the value represents—not how a trivial getter returns it. If a property calculates a value (like `Balance`), add a `` element that clarifies the calculation. +1. For each constructor, add `` plus `` elements describing the meaning of each argument, not just restating the parameter name. If one overload delegates to another, add a concise `` element. +1. For methods that can throw, add `` tags for each intentional exception type. Describe the condition that triggers it. Don't document exceptions thrown by argument validation helpers unless they're part of the public contract. +1. For methods that return a value, add `` with a short description of what callers receive. Avoid repeating the method name or managed type. +1. Work with the `BankAccount` base class first. + +When you're done, open the regenerated XML file and confirm that each member appears with your new elements. A trimmed portion might look like this: + +```xml + + Represents a bank account that records transactions and enforces an optional minimum balance. + Account numbers are generated sequentially when each instance is constructed. + + + Gets the current balance based on all recorded transactions. + The net sum of deposits and withdrawals. + +``` + +> [!TIP] +> Keep summaries to a single sentence. If you need more than one, move secondary context into ``. + +## Use `` in derived classes + +If you derive from `BankAccount` (for example, a `SavingsAccount` that applies interest), you can inherit base documentation instead of copying it. Add a self-closing `` element inside the derived member's documentation block. You can still append more elements (such as extra `` details) after `` to document the specialized behavior. + +```csharp +/// +/// Adds monthly interest during month-end processing. +public class SavingsAccount : BankAccount { /* ... */ } +``` + +> [!NOTE] +> `` reduces duplication and helps maintain consistency when you update base type documentation later. + +After you finish documenting the public surface, build one final time to confirm there are no remaining CS1591 warnings. Your project now produces useful IntelliSense and a structured XML file ready for publishing workflows. + +## Build output from comments + +You can explore more by trying any of these tools to create output from XML comments: + +- [DocFX](https://dotnet.github.io/docfx/): *DocFX* is an API documentation generator for .NET, which currently supports C#, Visual Basic, and F#. It also allows you to customize the generated reference documentation. DocFX builds a static HTML website from your source code and Markdown files. Also, DocFX provides you with the flexibility to customize the layout and style of your website through templates. You can also create custom templates. +- [Sandcastle](https://github.com/EWSoftware/SHFB): The *Sandcastle tools* create help files for managed class libraries containing both conceptual and API reference pages. The Sandcastle tools are command-line based and have no GUI front-end, project management features, or automated build process. The Sandcastle Help File Builder provides standalone GUI and command-line-based tools to build a help file in an automated fashion. A Visual Studio integration package is also available for it so that help projects can be created and managed entirely from within Visual Studio. +- [Doxygen](https://github.com/doxygen/doxygen): *Doxygen* generates an online documentation browser (in HTML) or an offline reference manual (in LaTeX) from a set of documented source files. There's also support for generating output in RTF (MS Word), PostScript, hyperlinked PDF, compressed HTML, DocBook, and Unix manual pages. You can configure Doxygen to extract the code structure from undocumented source files. + +## Related content + +- [XML Doc language reference](../../language-reference/xmldoc/index.md) +- [Recommended XML tags](../../language-reference/xmldoc/recommended-tags.md) +- [XML Documentation examples](../../language-reference/xmldoc/examples.md) diff --git a/docs/csharp/fundamentals/types/anonymous-types.md b/docs/csharp/fundamentals/types/anonymous-types.md index e8a619819a6dc..851dd8dda8d57 100644 --- a/docs/csharp/fundamentals/types/anonymous-types.md +++ b/docs/csharp/fundamentals/types/anonymous-types.md @@ -1,7 +1,7 @@ --- title: "Anonymous Types" description: Anonymous types in C# encapsulate a set of read-only properties in an object without having to explicitly define a type. The compiler generates a name. -ms.date: 05/14/2021 +ms.date: 10/13/2025 f1_keywords: - "anonymousObject_CSharpKeyword" helpviewer_keywords: @@ -10,7 +10,7 @@ helpviewer_keywords: --- # Anonymous types -Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first. The type name is generated by the compiler and is not available at the source code level. The type of each property is inferred by the compiler. +Anonymous types provide a convenient way to encapsulate a set of read-only properties into a single object without having to explicitly define a type first. The type name is generated by the compiler and isn't available at the source code level. The type of each property is inferred by the compiler. You create anonymous types by using the [`new`](../../language-reference/operators/new-operator.md) operator together with an object initializer. For more information about object initializers, see [Object and Collection Initializers](../../programming-guide/classes-and-structs/object-and-collection-initializers.md). @@ -26,9 +26,9 @@ Console.WriteLine(v.Amount + v.Message); Anonymous types are typically used in the [`select`](../../language-reference/keywords/select-clause.md) clause of a query expression to return a subset of the properties from each object in the source sequence. For more information about queries, see [LINQ in C#](../../linq/index.md). -Anonymous types contain one or more public read-only properties. No other kinds of class members, such as methods or events, are valid. The expression that is used to initialize a property cannot be `null`, an anonymous function, or a pointer type. +Anonymous types contain one or more public read-only properties. No other kinds of class members, such as methods or events, are valid. The expression that is used to initialize a property can't be `null`, an anonymous function, or a pointer type. -The most common scenario is to initialize an anonymous type with properties from another type. In the following example, assume that a class exists that is named `Product`. Class `Product` includes `Color` and `Price` properties, together with other properties that you are not interested in: +The most common scenario is to initialize an anonymous type with properties from another type. In the following example, assume that a class exists that is named `Product`. Class `Product` includes `Color` and `Price` properties, together with other properties that you aren't interested in: :::code language="csharp" source="snippets/anonymous-types/Program.cs" ID="ProductDefinition"::: @@ -44,7 +44,7 @@ Anonymous types support *projection initializers*, which allow you to use local :::code language="csharp" source="snippets/anonymous-types/Program.cs" ID="ProjectionInitializers"::: -This simplified syntax is particularly useful when creating anonymous types with many properties: +This simplified syntax is useful when creating anonymous types with many properties: :::code language="csharp" source="snippets/anonymous-types/Program.cs" ID="ProjectionExample"::: @@ -58,11 +58,11 @@ In these cases, you must explicitly specify the member name. > [!TIP] > You can use .NET style rule [IDE0037](../../../fundamentals/code-analysis/style-rules/ide0037.md) to enforce whether inferred or explicit member names are preferred. -It is also possible to define a field by object of another type: class, struct or even another anonymous type. It is done by using the variable holding this object just like in the following example, where two anonymous types are created using already instantiated user-defined types. In both cases the `product` field in the anonymous type `shipment` and `shipmentWithBonus` will be of type `Product` containing its default values of each field. And the `bonus` field will be of anonymous type created by the compiler. +It's also possible to define a field by object of another type: class, struct, or even another anonymous type. It's done by using the variable holding this object just like in the following example, where two anonymous types are created using already instantiated user-defined types. In both cases, the `product` field in the anonymous type `shipment` and `shipmentWithBonus` is of type `Product` containing its default values of each field. And the `bonus` field is of anonymous type created by the compiler. :::code language="csharp" source="snippets/anonymous-types/Program.cs" ID="snippet03"::: -Typically, when you use an anonymous type to initialize a variable, you declare the variable as an implicitly typed local variable by using [var](../../language-reference/statements/declarations.md#implicitly-typed-local-variables). The type name cannot be specified in the variable declaration because only the compiler has access to the underlying name of the anonymous type. For more information about `var`, see [Implicitly Typed Local Variables](../../programming-guide/classes-and-structs/implicitly-typed-local-variables.md). +Typically, when you use an anonymous type to initialize a variable, you declare the variable as an implicitly typed local variable by using [var](../../language-reference/statements/declarations.md#implicitly-typed-local-variables). The type name can't be specified in the variable declaration because only the compiler has access to the underlying name of the anonymous type. For more information about `var`, see [Implicitly Typed Local Variables](../../programming-guide/classes-and-structs/implicitly-typed-local-variables.md). You can create an array of anonymously typed elements by combining an implicitly typed local variable and an implicitly typed array, as shown in the following example. @@ -70,7 +70,7 @@ You can create an array of anonymously typed elements by combining an implicitly var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }}; ``` -Anonymous types are [`class`](../../language-reference/keywords/class.md) types that derive directly from [`object`](../../language-reference/builtin-types/reference-types.md), and that cannot be cast to any type except [`object`](../../language-reference/builtin-types/reference-types.md). The compiler provides a name for each anonymous type, although your application cannot access it. From the perspective of the common language runtime, an anonymous type is no different from any other reference type. +Anonymous types are [`class`](../../language-reference/keywords/class.md) types that derive directly from [`object`](../../language-reference/builtin-types/reference-types.md), and that can't be cast to any type except [`object`](../../language-reference/builtin-types/reference-types.md). The compiler provides a name for each anonymous type, although your application can't access it. From the perspective of the common language runtime, an anonymous type is no different from any other reference type. If two or more anonymous object initializers in an assembly specify a sequence of properties that are in the same order and that have the same names and types, the compiler treats the objects as instances of the same type. They share the same compiler-generated type information. @@ -78,12 +78,12 @@ Anonymous types support non-destructive mutation in the form of [with expression :::code language="csharp" source="snippets/anonymous-types/Program.cs" ID="snippet02"::: -You cannot declare a field, a property, an event, or the return type of a method as having an anonymous type. Similarly, you cannot declare a formal parameter of a method, property, constructor, or indexer as having an anonymous type. To pass an anonymous type, or a collection that contains anonymous types, as an argument to a method, you can declare the parameter as type `object`. However, using `object` for anonymous types defeats the purpose of strong typing. If you must store query results or pass them outside the method boundary, consider using an ordinary named struct or class instead of an anonymous type. +You can't declare a field, a property, an event, or the return type of a method as having an anonymous type. Similarly, you can't declare a formal parameter of a method, property, constructor, or indexer as having an anonymous type. To pass an anonymous type, or a collection that contains anonymous types, as an argument to a method, you can declare the parameter as type `object`. However, using `object` for anonymous types defeats the purpose of strong typing. If you must store query results or pass them outside the method boundary, consider using an ordinary named struct or class instead of an anonymous type. Because the and methods on anonymous types are defined in terms of the `Equals` and `GetHashCode` methods of the properties, two instances of the same anonymous type are equal only if all their properties are equal. > [!NOTE] -> The [accessibility level](../../programming-guide/classes-and-structs/access-modifiers.md) of an anonymous type is `internal`, hence two anonymous types defined in different assemblies are not of the same type. +> The [accessibility level](../../programming-guide/classes-and-structs/access-modifiers.md) of an anonymous type is `internal`. Hence, two anonymous types defined in different assemblies aren't of the same type. > Therefore instances of anonymous types can't be equal to each other when defined in different assemblies, even when having all their properties equal. Anonymous types do override the method, concatenating the name and `ToString` output of every property surrounded by curly braces. diff --git a/docs/csharp/fundamentals/types/classes.md b/docs/csharp/fundamentals/types/classes.md index 32f51ac17f7cb..67a764635fa97 100644 --- a/docs/csharp/fundamentals/types/classes.md +++ b/docs/csharp/fundamentals/types/classes.md @@ -1,7 +1,7 @@ --- title: "Classes in the C# type system." description: Learn about class types, how to use classes, and how to create new class type declarations for your app. -ms.date: 08/15/2024 +ms.date: 10/10/2025 helpviewer_keywords: - "classes [C#]" - "C# language, classes" @@ -13,14 +13,14 @@ helpviewer_keywords: A type that is defined as a [`class`](../../language-reference/keywords/class.md) is a *reference type*. At run time, when you declare a variable of a reference type, the variable contains the value [`null`](../../language-reference/keywords/null.md) until you explicitly create an instance of the class by using the [`new`](../../language-reference/operators/new-operator.md) operator, or assign it an object of a compatible type created elsewhere, as shown in the following example: ```csharp -//Declaring an object of type MyClass. +// Declaring an object of type MyClass. MyClass mc = new MyClass(); -//Declaring another object of the same type, assigning it the value of the first object. +// Declaring another object of the same type, assigning it the value of the first object. MyClass mc2 = mc; ``` -When the object is created, enough memory is allocated on the managed heap for that specific object, and the variable holds only a reference to the location of said object. The memory used by an object is reclaimed by the automatic memory management functionality of the CLR, which is known as *garbage collection*. For more information about garbage collection, see [Automatic memory management and garbage collection](../../../standard/garbage-collection/fundamentals.md). +When the object is created, enough memory is allocated on the managed heap for that specific object, and the variable holds only a reference to the location of said object. The memory used by an object is reclaimed by the automatic memory management functionality of the Common Language Runtime (CLR), which is known as *garbage collection*. For more information about garbage collection, see [Automatic memory management and garbage collection](../../../standard/garbage-collection/fundamentals.md). ## Declaring classes diff --git a/docs/csharp/fundamentals/types/generics.md b/docs/csharp/fundamentals/types/generics.md index 020db9863af1a..f3842db7619a5 100644 --- a/docs/csharp/fundamentals/types/generics.md +++ b/docs/csharp/fundamentals/types/generics.md @@ -1,7 +1,7 @@ --- title: "Generic classes and methods" description: Learn about generics. Generic types maximize code reuse, type safety, and performance, and are commonly used to create collection classes. -ms.date: 03/14/2024 +ms.date: 10/10/2025 f1_keywords: - "generics_CSharpKeyword" helpviewer_keywords: diff --git a/docs/csharp/fundamentals/types/index.md b/docs/csharp/fundamentals/types/index.md index 47b6991810d3e..381a615c2adb3 100644 --- a/docs/csharp/fundamentals/types/index.md +++ b/docs/csharp/fundamentals/types/index.md @@ -1,7 +1,7 @@ --- title: "Learn the fundamentals of the C# type system" description: Learn about creating types in C#, such as tuples, records, value types, and reference types. Learn to choose between these options. -ms.date: 08/15/2024 +ms.date: 10/10/2025 helpviewer_keywords: - "value types [C#]" - "reference types [C#]" @@ -30,7 +30,7 @@ The compiler uses type information to make sure all operations that are performe :::code language="csharp" source="../../programming-guide/types/snippets/index/Program.cs" ID="TypeSafeExample"::: > [!NOTE] -> C and C++ developers, notice that in C#, `bool` is not convertible to `int`. +> C and C++ developers, notice that in C#, `bool` isn't convertible to `int`. The compiler embeds the type information into the executable file as metadata. The common language runtime (CLR) uses that metadata at run time to further guarantee type safety when it allocates and reclaims memory. @@ -78,7 +78,7 @@ The following illustration shows the relationship between value types and refere ![Screenshot that shows CTS value types and reference types.](../../programming-guide/types/media/index/value-reference-types-common-type-system.png) > [!NOTE] -> You can see that the most commonly used types are all organized in the namespace. However, the namespace in which a type is contained has no relation to whether it is a value type or reference type. +> You can see that the most commonly used types are all organized in the namespace. However, the namespace in which a type is contained has no relation to whether it's a value type or reference type. Classes and structs are two of the basic constructs of the common type system in .NET. Each is essentially a data structure that encapsulates a set of data and behaviors that belong together as a logical unit. The data and behaviors are the *members* of the class, struct, or record. The members include its methods, properties, events, and so on, as listed later in this article. @@ -90,7 +90,7 @@ A struct is a value type. When a struct is created, the variable to which the st Record types can be either reference types (`record class`) or value types (`record struct`). Record types contain methods that support value-equality. -In general, classes are used to model more complex behavior. Classes typically store data that is intended to be modified after a class object is created. Structs are best suited for small data structures. Structs typically store data that isn't intended to be modified after the struct is created. Record types are data structures with additional compiler synthesized members. Records typically store data that isn't intended to be modified after the object is created. +In general, classes are used to model more complex behavior. Classes typically store data that is intended to be modified after a class object is created. Structs are best suited for small data structures. Structs typically store data that isn't intended to be modified after the struct is created. Record types are data structures with extra compiler synthesized members. Records typically store data that isn't intended to be modified after the object is created. ### Value types diff --git a/docs/csharp/fundamentals/types/interfaces.md b/docs/csharp/fundamentals/types/interfaces.md index 1e971ddc8b432..7228c8a09faec 100644 --- a/docs/csharp/fundamentals/types/interfaces.md +++ b/docs/csharp/fundamentals/types/interfaces.md @@ -1,14 +1,14 @@ --- title: "Interfaces - define behavior for multiple types" description: An interface in C# contains definitions for a group of related functionalities that a non-abstract class or a struct must implement. It specifies the members and their signatures for a type that implements the interface. -ms.date: 05/14/2021 +ms.date: 10/13/2025 helpviewer_keywords: - "interfaces [C#]" - "C# language, interfaces" --- # Interfaces - define behavior for multiple types -An interface contains definitions for a group of related functionalities that a non-abstract [`class`](../../language-reference/keywords/class.md) or a [`struct`](../../language-reference/builtin-types/struct.md) must implement. An interface may define `static` methods, which must have an implementation. An interface may define a default implementation for members. An interface may not declare instance data such as fields, automatically implemented properties, or property-like events. +An interface contains definitions for a group of related functionalities that a non-abstract [`class`](../../language-reference/keywords/class.md) or a [`struct`](../../language-reference/builtin-types/struct.md) must implement. An interface can define `static` methods. An interface can define a default implementation for members. An interface can't declare instance data such as fields, automatically implemented properties, or property-like events. By using interfaces, you can, for example, include behavior from multiple sources in a class. That capability is important in C# because the language doesn't support multiple inheritance of classes. In addition, you must use an interface if you want to simulate inheritance for structs, because they can't actually inherit from another struct or class. @@ -24,14 +24,14 @@ The definition of `IEquatable` doesn't provide an implementation for `Equals` For more information about abstract classes, see [Abstract and Sealed Classes and Class Members](../../programming-guide/classes-and-structs/abstract-and-sealed-classes-and-class-members.md). -Interfaces can contain instance methods, properties, events, indexers, or any combination of those four member types. Interfaces may contain static constructors, fields, constants, or operators. Beginning with C# 11, interface members that aren't fields may be `static abstract`. An interface can't contain instance fields, instance constructors, or finalizers. Interface members are public by default, and you can explicitly specify accessibility modifiers, such as `public`, `protected`, `internal`, `private`, `protected internal`, or `private protected`. A `private` member must have a default implementation. +Interfaces can contain instance methods, properties, events, indexers, or any combination of those four member types. Interfaces can contain static constructors, fields, constants, or operators. Beginning with C# 11, interface members that aren't fields can be `static abstract`. An interface can't contain instance fields, instance constructors, or finalizers. Interface members are public by default, and you can explicitly specify accessibility modifiers, such as `public`, `protected`, `internal`, `private`, `protected internal`, or `private protected`. A `private` member must have a default implementation. To implement an interface member using implicit implementation, the corresponding member of the implementing class must be public, non-static, and have the same name and signature as the interface member. However, when an interface is meant to be internal only or uses internal types in its signature, you can use explicit interface implementation instead, which doesn't require the implementing member to be public. > [!NOTE] -> When an interface declares static members, a type implementing that interface may also declare static members with the same signature. Those are distinct and uniquely identified by the type declaring the member. The static member declared in a type *doesn't* override the static member declared in the interface. +> When an interface declares static members, a type implementing that interface might also declare static members with the same signature. Those are distinct and uniquely identified by the type declaring the member. The static member declared in a type *doesn't* override the static member declared in the interface. -A class or struct that implements an interface must provide an implementation for all declared members without a default implementation provided by the interface. However, if a base class implements an interface, any class that's derived from the base class inherits that implementation. +A class or struct that implements an interface must provide an implementation for all declared members without a default implementation provided by the interface. However, if a base class implements an interface, any class derived from the base class inherits that implementation. The following example shows an implementation of the interface. The implementing class, `Car`, must provide an implementation of the method. @@ -39,7 +39,7 @@ The following example shows an implementation of the class Product { - public string? Color {get;set;} - public decimal Price {get;set;} - public string? Name {get;set;} - public string? Category {get;set;} - public string? Size {get;set;} + public string? Color { get; set; } + public decimal Price { get; set; } + public string? Name { get; set; } + public string? Category { get; set; } + public string? Size { get; set; } } // class Anonymous @@ -21,10 +21,10 @@ static void Main() // Don't show this unless you add a bunch more // properties to the type. Otherwise it obviates the // need for the anonymous type. - List products = new () - { - new Product() { Color="Orange", Price=2.00M}, - }; + List products = + [ + new Product { Color = "Orange", Price = 2.00M } + ]; // var productQuery = @@ -54,12 +54,12 @@ from prod in products // // Explicit member names. var personExplicit = new { FirstName = "Kyle", LastName = "Mit" }; - + // Projection initializers (inferred member names). var firstName = "Kyle"; var lastName = "Mit"; var personInferred = new { firstName, lastName }; - + // Both create equivalent anonymous types with the same property names. Console.WriteLine($"Explicit: {personExplicit.FirstName} {personExplicit.LastName}"); Console.WriteLine($"Inferred: {personInferred.firstName} {personInferred.lastName}"); @@ -69,13 +69,13 @@ from prod in products var title = "Software Engineer"; var department = "Engineering"; var salary = 75000; - + // Using projection initializers. var employee = new { title, department, salary }; - + // Equivalent to explicit syntax: // var employee = new { title = title, department = department, salary = salary }; - + Console.WriteLine($"Title: {employee.title}, Department: {employee.department}, Salary: {employee.salary}"); // } diff --git a/docs/csharp/fundamentals/types/snippets/classes/Program.cs b/docs/csharp/fundamentals/types/snippets/classes/Program.cs index 8ad6131e85b3e..c56fe653fc561 100644 --- a/docs/csharp/fundamentals/types/snippets/classes/Program.cs +++ b/docs/csharp/fundamentals/types/snippets/classes/Program.cs @@ -1,6 +1,5 @@ - -// -Customer object1 = new Customer(); +// +Customer object1 = new(); // // @@ -9,14 +8,14 @@ // -Customer object3 = new Customer(); +Customer object3 = new(); Customer object4 = object3; // /* var p1 = new Person(); // Error! Required properties not set */ -var p2 = new Person() {FirstName = "Grace", LastName = "Hopper" }; +var p2 = new Person { FirstName = "Grace", LastName = "Hopper" }; // //[access modifier] - [class] - [identifier] diff --git a/docs/csharp/language-reference/compiler-messages/cs0552.md b/docs/csharp/language-reference/compiler-messages/cs0552.md deleted file mode 100644 index 934d730087623..0000000000000 --- a/docs/csharp/language-reference/compiler-messages/cs0552.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -description: "Compiler Error CS0552" -title: "Compiler Error CS0552" -ms.date: 07/20/2015 -f1_keywords: - - "CS0552" -helpviewer_keywords: - - "CS0552" -ms.assetid: ce5cfb26-8406-4ca0-adb7-55d1d03d8145 ---- -# Compiler Error CS0552 - -'conversion routine' : user defined conversion to/from interface - - You cannot create a user-defined conversion to or from an interface. If you need the conversion routine, resolve this error by making the interface a class or derive a class from the interface. - - The following sample generates CS0552: - -```csharp -// CS0552.cs -public interface ii -{ -} - -public class a -{ - // delete the routine to resolve CS0552 - public static implicit operator ii(a aa) // CS0552 - { - return new ii(); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/language-reference/compiler-messages/cs0563.md b/docs/csharp/language-reference/compiler-messages/cs0563.md deleted file mode 100644 index a6edbab270f38..0000000000000 --- a/docs/csharp/language-reference/compiler-messages/cs0563.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: "Compiler Error CS0563" -title: "Compiler Error CS0563" -ms.date: 07/20/2015 -f1_keywords: - - "CS0563" -helpviewer_keywords: - - "CS0563" -ms.assetid: c1561e4e-7f00-41ff-abff-b8228aee66a4 ---- -# Compiler Error CS0563 - -One of the parameters of a binary operator must be the containing type - -The method declaration for an [operator overload](../operators/operator-overloading.md) must follow certain guidelines. - -## Example - - The following sample generates CS0563: - -```csharp -// CS0563.cs -public class iii -{ - public static implicit operator int(iii x) - { - return 0; - } - public static implicit operator iii(int x) - { - return null; - } - public static int operator +(int aa, int bb) // CS0563 - // Use the following line instead: - // public static int operator +(int aa, iii bb) - { - return 0; - } - public static void Main() - { - } -} -``` diff --git a/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md b/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md new file mode 100644 index 0000000000000..b356db406a1d7 --- /dev/null +++ b/docs/csharp/language-reference/compiler-messages/overloaded-operator-errors.md @@ -0,0 +1,368 @@ +--- +title: Errors and warnings related to user defined operator declarations +description: This article helps you diagnose and correct compiler errors and warnings when you declare user defined operators in your types +f1_keywords: + - "CS0056" + - "CS0057" + - "CS0215" + - "CS0216" + - "CS0217" + - "CS0218" + - "CS0448" + - "CS0552" + - "CS0553" + - "CS0554" + - "CS0555" + - "CS0556" + - "CS0557" + - "CS0558" + - "CS0559" + - "CS0562" + - "CS0563" + - "CS0564" + - "CS0567" + - "CS0590" + - "CS0660" + - "CS0661" + - "CS0715" + - "CS1037" + - "CS1553" + - "CS8930" + - "CS8931" + - "CS9023" + - "CS9024" + - "CS9025" + - "CS9308" + - "CS9310" + - "CS9311" + - "CS9312" + - "CS9313" +helpviewer_keywords: + - "CS0056" + - "CS0057" + - "CS0215" + - "CS0216" + - "CS0217" + - "CS0218" + - "CS0448" + - "CS0552" + - "CS0553" + - "CS0554" + - "CS0555" + - "CS0556" + - "CS0557" + - "CS0558" + - "CS0559" + - "CS0562" + - "CS0563" + - "CS0564" + - "CS0567" + - "CS0590" + - "CS0660" + - "CS0661" + - "CS0715" + - "CS1037" + - "CS1553" + - "CS8930" + - "CS8931" + - "CS9023" + - "CS9024" + - "CS9025" + - "CS9308" + - "CS9310" + - "CS9311" + - "CS9312" + - "CS9313" +ms.date: 10/15/2025 +ai-usage: ai-assisted +--- +# Errors and warnings for overloaded, or user-defined operator declarations + +There are several errors related to declaring overloaded operators. Overloaded operators are also referred to as user-defined operators + + +- [**CS0056**](#inconsistent-accessibility): *Inconsistent accessibility: return type 'type' is less accessible than operator 'operator'* +- [**CS0057**](#inconsistent-accessibility): *Inconsistent accessibility: parameter type 'type' is less accessible than operator 'operator'* +- [**CS0215**](#boolean-and-short-circuit-operators): *The return type of operator True or False must be bool* +- [**CS0216**](#boolean-and-short-circuit-operators): *The operator 'operator' requires a matching operator 'missing_operator' to also be defined* +- [**CS0217**](#boolean-and-short-circuit-operators): *In order to be applicable as a short circuit operator a user-defined logical operator ('operator') must have the same return type as the type of its 2 parameters.* +- [**CS0218**](#boolean-and-short-circuit-operators): *The type ('type') must contain declarations of operator true and operator false* +- [**CS0448**](#operator-signature-requirements): *The return type for `++` or `--` operator must be the containing type or derived from the containing type* +- [**CS0552**](#user-defined-conversion-restrictions): *'conversion routine' : user defined conversion to/from interface* +- [**CS0553**](#user-defined-conversion-restrictions): *'conversion routine' : user defined conversion to/from base class* +- [**CS0554**](#user-defined-conversion-restrictions): *'conversion routine' : user defined conversion to/from derived class* +- [**CS0555**](#user-defined-conversion-restrictions): *User-defined operator cannot take an object of the enclosing type and convert to an object of the enclosing type* +- [**CS0556**](#user-defined-conversion-restrictions): *User-defined conversion must convert to or from the enclosing type* +- [**CS0557**](#user-defined-conversion-restrictions): *Duplicate user-defined conversion in type* +- [**CS0558**](#operator-declaration-requirements): *User-defined operator must be declared static and public* +- [**CS0559**](#operator-signature-requirements): *The parameter type for `++` or `--` operator must be the containing type* +- [**CS0562**](#operator-signature-requirements): *The parameter of a unary operator must be the containing type* +- [**CS0563**](#operator-signature-requirements): *One of the parameters of a binary operator must be the containing type* +- [**CS0564**](#operator-signature-requirements): *The first operand of an overloaded shift operator must have the same type as the containing type, and the type of the second operand must be int* +- [**CS0567**](#operator-signature-requirements): *Interfaces cannot contain operators* +- [**CS0590**](#operator-signature-requirements): *User-defined operators cannot return void* +- [**CS0660**](#equality-operators): *Type defines `operator ==` or `operator !=` but does not override `Object.Equals(object o)`* +- [**CS0661**](#equality-operators): *Type defines `operator ==` or `operator !=` but does not override `Object.GetHashCode()`* +- [**CS0715**](#operator-declaration-requirements): *Static classes cannot contain user-defined operators* +- [**CS1037**](#operator-declaration-requirements): *Overloadable operator expected* +- [**CS1553**](#operator-declaration-requirements): *Declaration is not valid; use 'modifier operator \ (...' instead* +- [**CS8930**](#operator-declaration-requirements): *Explicit implementation of a user-defined operator must be static.* +- [**CS8931**](#operator-declaration-requirements): *Explicit implementation must be declared public to implement interface member in type.* +- [**CS9023**](#checked-operators): *Operator cannot be made checked.* +- [**CS9024**](#checked-operators): *Operator cannot be made unchecked.* +- [**CS9025**](#checked-operators): *Operator requires a matching non-checked version to also be declared.* +- [**CS9308**](#operator-declaration-requirements): *User-defined operator must be declared public.* +- [**CS9310**](#operator-signature-requirements): *The return type for this operator must be void.* +- [**CS9311**](#interface-and-inheritance-requirements): *Type does not implement interface member. The type cannot implement member because one of them is not an operator.* +- [**CS9312**](#interface-and-inheritance-requirements): *Type cannot override inherited member because one of them is not an operator.* +- [**CS9313**](#interface-and-inheritance-requirements): *Overloaded compound assignment operator takes one parameter.* + +The following sections provide examples of common issues and how to fix them. + +## Operator signature requirements + +- **CS0448**: *The return type for `++` or `--` operator must be the containing type or derived from the containing type.* +- **CS0559**: *The parameter type for `++` or `--` operator must be the containing type.* +- **CS0562**: *The parameter of a unary operator must be the containing type.* +- **CS0563**: *One of the parameters of a binary operator must be the containing type.* +- **CS0564**: *The first operand of an overloaded shift operator must have the same type as the containing type, and the type of the second operand must be int.* +- **CS0567**: *Interfaces can't contain operators.* +- **CS0590**: *User-defined operators can't return void.* +- **CS9310**: *The return type for this operator must be void.* + +These errors occur when operator declarations don't follow the required signature rules. Each operator type has specific requirements for parameter types and return types. + +> [!IMPORTANT] +> The signature requirements for static binary operators and the corresponding instance compound assignment operators are different. Make sure the signature matches the declaration you want. + +For more information, see [Operator overloading](../operators/operator-overloading.md). The following example demonstrates these errors: + +```csharp +class C1 +{ + public static int operator ++(C1 c) => 0; // CS0448 + public static C1 operator --(C1 c) => null; // OK +} +public class C2 +{ + public static implicit operator int(C2 x) => 0; + public static implicit operator C2(int x) => new C2(); + public static int operator ++(int aa) => 0; // CS0559 +} +public class C3 +{ + public static implicit operator int(C3 x) => 0; + public static implicit operator C3(int x) => null; + public static C3 operator +(int aa) => 0; // CS0562 +} +public class C4 +{ + public static implicit operator int(C4 x) => 0; + public static implicit operator C4(int x) => null; + public static int operator +(int aa, int bb) => 0; // CS0563 +} +class C5 +{ + // To correct, change second operand to int, like so: + // public static int operator << (C c1, int c2) + public static int operator <<(C5 c1, C5 c2) => 0; // CS0564 +} +interface IA +{ + int operator +(int aa, int bb); // CS0567 +} +public class C6 +{ + public static void operator +(C6 A1, C6 A2) { } // CS0590 +} +``` + +To fix these errors, ensure your operator declarations follow the signature requirements for the specific operator type you're overloading. + +## Operator declaration requirements + +- **CS0558**: *User-defined operator must be declared static and public.* +- **CS0715**: *Static classes can't contain user-defined operators.* +- **CS1037**: *Overloadable operator expected.* +- **CS1553**: *Declaration isn't valid; use 'modifier operator \ (...' instead.* +- **CS8930**: *Explicit implementation of a user-defined operator must be static.* +- **CS8931**: *Explicit implementation must be declared public to implement interface member in type.* +- **CS9308**: *User-defined operator must be declared public.* + +These errors occur when operator declarations don't use the required modifiers or syntax. Most user-defined operators must be both `static` and `public`, and conversion operators require specific syntax. For more information, see [Operator overloading](../operators/operator-overloading.md) and [User-defined conversion operators](../operators/user-defined-conversion-operators.md). The following code demonstrates these errors: + +```csharp +public class C +{ + static implicit operator int(C aa) => 0; // CS0558, add public +} +public static class C1 +{ + public static int operator +(C1 c) => 0; // CS0715 +} +class C2 +{ + public static int implicit operator (C2 f) => 6; // CS1553 +} +``` + +To fix these errors, ensure your operator declarations include the required `static` and `public` modifiers, follow the correct syntax for conversion operators, and don't declare operators in static classes. + +## Inconsistent accessibility + +- **CS0056**: *Inconsistent accessibility: return type 'type' is less accessible than operator 'operator'.* +- **CS0057**: *Inconsistent accessibility: parameter type 'type' is less accessible than operator 'operator'.* + +These errors occur when you declare a public operator with return types or parameter types that have more restrictive accessibility than the operator itself. All public constructs must use publicly accessible types for their parameters and return values. For more information, see [Access Modifiers](../../programming-guide/classes-and-structs/access-modifiers.md). + +The following code snippets demonstrate these errors: + +```csharp +class C { } + +public class C2 +{ + public static implicit operator C(C2 a) => new C(); // CS0056 +} + +public class C3 +{ + public static implicit operator C3(C c) => new C3(); // CS0057 +} +``` + +To fix these errors, make sure all types used in public operator declarations are also publicly accessible. + +## User-defined conversion restrictions + +- **CS0552**: *User-defined conversion to/from interface.* +- **CS0553**: *User-defined conversion to/from base class.* +- **CS0554**: *User-defined conversion to/from derived class.* +- **CS0555**: *User-defined operator can't take an object of the enclosing type and convert to an object of the enclosing type.* +- **CS0556**: *User-defined conversion must convert to or from the enclosing type.* +- **CS0557**: *Duplicate user-defined conversion in type.* + +These errors occur when you attempt to create invalid user-defined conversion operators. Conversion operators have specific restrictions about which types they can convert between. For more information, see [User-defined conversion operators](../operators/user-defined-conversion-operators.md). The following code demonstrates the preceding errors: + +```csharp +public interface I +{ +} +public class C +{ + public static implicit operator I(C aa) => default;// CS0552 +} + +public class B +{ +} +public class D : B +{ + public static implicit operator B(D aa) => new B();// CS0553 +} + +public class B2 +{ + // delete the conversion routine to resolve CS0554 + public static implicit operator B2(D2 d) => new B2();// CS0554 +} +public class D2 : B2 { } + +public class C2 +{ + public static implicit operator C2(C2 aa) => new C2(); // CS0555 +} + +public class C3 +{ + public static implicit operator int(byte aa) => 0; // CS0556 +} + +public class C4 +{ + public static implicit operator int(C4 aa) => 0; + + // CS0557, delete duplicate + public static explicit operator int(C4 aa) => 0; +} +``` + +To fix these errors, remove invalid conversion operators or restructure your type hierarchy to avoid the restricted conversion patterns. + +## Boolean and short-circuit operators + +- **CS0215**: *The return type of operator true or false must be bool.* +- **CS0216**: *The operator requires a matching operator to also be defined.* +- **CS0217**: *In order to be applicable as a short-circuit operator, a user-defined logical operator must have the same return type as the type of its 2 parameters.* +- **CS0218**: *The type must contain declarations of operator true and operator false.* + +These errors occur when you define logical operators incorrectly. Certain operators must be defined in pairs, and short-circuit operators have specific signature requirements. For more information, see [true and false operators](../operators/true-false-operators.md), [Boolean logical operators](../operators/boolean-logical-operators.md), and [User-defined conditional logical operators](~/_csharpstandard/standard/expressions.md#12153-user-defined-conditional-logical-operators). The following code demonstrates these errors: + +```csharp +class C +{ + public static int operator true(C c) => 0; // CS0215 + public static int operator false(C c) => 0; // CS0215 +} + +class C2 +{ + public static bool operator ==(C2 left, C2 right) => left.Equals(right); // CS0216 + + public override bool Equals(object? o) => base.Equals(o); + public override int GetHashCode() => base.GetHashCode(); +} + +public class C3 +{ + public static bool operator true(C3 f) => false; + public static bool operator false(C3 f) => true; + public static implicit operator int(C3 x) => 0; + public static int operator &(C3 f1, C3 f2) => new C3(); // CS0217 +} + +public class C4 +{ + public static implicit operator int(C4 x) => 0; + public static C4 operator &(C4 f1, C4 f2) => new C4(); + + public static void Main() + { + C4 f = new C4(); + int i = f && f; // CS0218, requires operators true and false + } +} +``` + +To fix these errors, ensure you define required paired operators and follow the correct signature patterns for logical operators. + +## Checked operators + +- **CS9023**: *Operator can't be made checked* +- **CS9024**: *Operator can't be made unchecked* +- **CS9025**: *Checked operator requires a matching non-checked version to also be declared* + +These errors occur when you incorrectly use the `checked` or `unchecked` keywords with operator declarations. Not all operators support checked/unchecked variants, and when they do, certain requirements must be met. For more information, see [Arithmetic operators](../operators/arithmetic-operators.md#user-defined-checked-operators) and [User-defined checked operators](~/_csharplang/proposals/csharp-11.0/checked-user-defined-operators.md). + +To fix these errors, either remove the `checked` or `unchecked` keyword from operators that don't support it, or ensure you provide both checked and non-checked versions when required. + +## Interface and inheritance requirements + +- **CS9311**: *Type doesn't implement interface member. The type can't implement member because one of them isn't an operator* +- **CS9312**: *Type can't override inherited member because one of them isn't an operator* +- **CS9313**: *Overloaded compound assignment operator takes one parameter* + +These errors occur when there are mismatches between operator declarations and interface implementations or inheritance relationships. Operators have specific rules for interface implementation and overriding. For more information, see [Operator overloading](../operators/operator-overloading.md) and [Interfaces](../../fundamentals/types/interfaces.md). + +To fix these errors, ensure that operator declarations correctly match interface requirements and follow the rules for operator overriding and compound assignment operators. + +## Equality operators + +- **CS0660**: *Type defines operator == or operator != but doesn't override Object.Equals(object o)* +- **CS0661**: *Type defines operator == or operator != but doesn't override Object.GetHashCode()* + +These warnings occur when you define equality or inequality operators without also overriding the corresponding methods from . When you define custom equality comparison, you should also override and to ensure consistent behavior. For more information, see [How to define value equality for a type](../../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md) and [Equality operators](../operators/equality-operators.md). + +To fix these warnings, override both `Equals` and `GetHashCode` when you define custom equality operators. diff --git a/docs/csharp/language-reference/toc.yml b/docs/csharp/language-reference/toc.yml index 7e3f7d8b51acd..92f54bc76877b 100644 --- a/docs/csharp/language-reference/toc.yml +++ b/docs/csharp/language-reference/toc.yml @@ -484,6 +484,13 @@ items: CS0514, CS0515, CS0516, CS0517, CS0522, CS0526, CS0568, CS0710, CS0768, CS0824, CS8054, CS8091, CS8358, CS8862, CS8867, CS8868, CS8878, CS8910, CS8958, CS8982, CS8983, CS9105, CS9106, CS9107, CS9108, CS9109, CS9110, CS9111, CS9112, CS9113, CS9114, CS9115, CS9116, CS9117, CS9118, CS9119, CS9120, CS9121, CS9122, CS9124, CS9136, CS9179 + - name: Operator declarations + href: ./compiler-messages/overloaded-operator-errors.md + displayName: > + operator, overloaded operator, user defined operator, + CS0056, CS0057, CS0215, CS0216, CS0217, CS0218, CS0448, CS0552, CS0553, CS0554, CS0555, CS0556, CS0557, CS0558, CS0559, + CS0562, CS0563, CS0564, CS0567, CS0590, CS0660, CS0661, CS0715, CS1037, CS1553, CS1554, CS8930, CS8931, CS9023, CS9024, + CS9025, CS9308, CS9310, CS9311, CS9312, CS9313 - name: Parameter / argument mismatch href: ./compiler-messages/parameter-argument-mismatch.md displayName: > @@ -669,10 +676,6 @@ items: href: ../misc/cs0054.md - name: CS0055 href: ../misc/cs0055.md - - name: CS0056 - href: ../misc/cs0056.md - - name: CS0057 - href: ../misc/cs0057.md - name: CS0058 href: ../misc/cs0058.md - name: CS0059 @@ -867,14 +870,6 @@ items: href: ../misc/cs0213.md - name: CS0214 href: ../misc/cs0214.md - - name: CS0215 - href: ../misc/cs0215.md - - name: CS0216 - href: ../misc/cs0216.md - - name: CS0217 - href: ../misc/cs0217.md - - name: CS0218 - href: ../misc/cs0218.md - name: CS0220 href: ../misc/cs0220.md - name: CS0221 @@ -1027,8 +1022,6 @@ items: href: ./compiler-messages/cs0446.md - name: CS0447 href: ../misc/cs0447.md - - name: CS0448 - href: ../misc/cs0448.md - name: CS0449 href: ../misc/cs0449.md - name: CS0450 @@ -1135,30 +1128,6 @@ items: href: ../misc/cs0550.md - name: CS0551 href: ../misc/cs0551.md - - name: CS0552 - href: ./compiler-messages/cs0552.md - - name: CS0553 - href: ../misc/cs0553.md - - name: CS0554 - href: ../misc/cs0554.md - - name: CS0555 - href: ../misc/cs0555.md - - name: CS0556 - href: ../misc/cs0556.md - - name: CS0557 - href: ../misc/cs0557.md - - name: CS0558 - href: ../misc/cs0558.md - - name: CS0559 - href: ../misc/cs0559.md - - name: CS0562 - href: ../misc/cs0562.md - - name: CS0563 - href: ./compiler-messages/cs0563.md - - name: CS0564 - href: ../misc/cs0564.md - - name: CS0567 - href: ../misc/cs0567.md - name: CS0569 href: ../misc/cs0569.md - name: CS0570 @@ -1195,8 +1164,6 @@ items: href: ../misc/cs0588.md - name: CS0589 href: ../misc/cs0589.md - - name: CS0590 - href: ../misc/cs0590.md - name: CS0592 href: ./compiler-messages/cs0592.md - name: CS0594 @@ -1311,8 +1278,6 @@ items: href: ../misc/cs0713.md - name: CS0714 href: ../misc/cs0714.md - - name: CS0715 - href: ../misc/cs0715.md - name: CS0716 href: ../misc/cs0716.md - name: CS0717 @@ -1451,8 +1416,6 @@ items: href: ../misc/cs1035.md - name: CS1036 href: ../misc/cs1036.md - - name: CS1037 - href: ../misc/cs1037.md - name: CS1038 href: ../misc/cs1038.md - name: CS1040 @@ -1541,10 +1504,6 @@ items: href: ./compiler-messages/cs1548.md - name: CS1551 href: ../misc/cs1551.md - - name: CS1553 - href: ../misc/cs1553.md - - name: CS1554 - href: ../misc/cs1554.md - name: CS1555 href: ../misc/cs1555.md - name: CS1556 @@ -2235,10 +2194,6 @@ items: href: ../misc/cs0642.md - name: CS0659 href: ../misc/cs0659.md - - name: CS0660 - href: ../misc/cs0660.md - - name: CS0661 - href: ../misc/cs0661.md - name: CS0665 href: ../misc/cs0665.md - name: CS0675 diff --git a/docs/csharp/misc/cs0056.md b/docs/csharp/misc/cs0056.md deleted file mode 100644 index db4f9a1055111..0000000000000 --- a/docs/csharp/misc/cs0056.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: "Compiler Error CS0056" -title: "Compiler Error CS0056" -ms.date: 07/20/2015 -f1_keywords: - - "CS0056" -helpviewer_keywords: - - "CS0056" -ms.assetid: 8878b09c-5b7b-40e0-be0d-61ef5b36c151 ---- -# Compiler Error CS0056 - -Inconsistent accessibility: return type 'type' is less accessible than operator 'operator' - - A public construct must return a publicly accessible object. For more information, see [Access Modifiers](../programming-guide/classes-and-structs/access-modifiers.md). - - The following sample generates CS0056: - -```csharp -// CS0056.cs -class MyClass -// try the following line instead -// public class MyClass -{ -} - -public class A -{ - public static implicit operator MyClass(A a) // CS0056 - { - return new MyClass(); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0057.md b/docs/csharp/misc/cs0057.md deleted file mode 100644 index c2d3e560fc210..0000000000000 --- a/docs/csharp/misc/cs0057.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: "Compiler Error CS0057" -title: "Compiler Error CS0057" -ms.date: 07/20/2015 -f1_keywords: - - "CS0057" -helpviewer_keywords: - - "CS0057" -ms.assetid: 0bdd628f-7a1f-4209-bb28-c4a66eb3bf1d ---- -# Compiler Error CS0057 - -Inconsistent accessibility: parameter type 'type' is less accessible than operator 'operator' - - A public construct must return a publicly accessible object. For more information, see [Access Modifiers](../programming-guide/classes-and-structs/access-modifiers.md). - - The following sample generates CS0057: - -```csharp -// CS0057.cs -class MyClass //defaults to private accessibility -// try the following line instead -// public class MyClass -{ -} - -public class MyClass2 -{ - public static implicit operator MyClass2(MyClass iii) // CS0057 - { - return new MyClass2(); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0215.md b/docs/csharp/misc/cs0215.md deleted file mode 100644 index d34e09ff46409..0000000000000 --- a/docs/csharp/misc/cs0215.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -description: "Compiler Error CS0215" -title: "Compiler Error CS0215" -ms.date: 07/20/2015 -f1_keywords: - - "CS0215" -helpviewer_keywords: - - "CS0215" -ms.assetid: 2060440d-be22-4c10-8b26-43b08b615447 ---- -# Compiler Error CS0215 - -The return type of operator True or False must be bool - -User-defined [true and false](../language-reference/operators/true-false-operators.md) operators must have a return type of [bool](../language-reference/builtin-types/bool.md). - -The following sample generates CS0215: - -```csharp -// CS0215.cs -class MyClass -{ - public static int operator true (MyClass MyInt) // CS0215 - // try the following line instead - // public static bool operator true (MyClass MyInt) - { - return true; - } - - public static int operator false (MyClass MyInt) // CS0215 - // try the following line instead - // public static bool operator false (MyClass MyInt) - { - return true; - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0216.md b/docs/csharp/misc/cs0216.md deleted file mode 100644 index d695d4e170acb..0000000000000 --- a/docs/csharp/misc/cs0216.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -description: "Compiler Error CS0216" -title: "Compiler Error CS0216" -ms.date: 07/20/2015 -f1_keywords: - - "CS0216" -helpviewer_keywords: - - "CS0216" -ms.assetid: afb3dd29-3eff-4b62-8267-eb726c2bcee4 ---- -# Compiler Error CS0216 - -The operator 'operator' requires a matching operator 'missing_operator' to also be defined - - A user-defined [==](../language-reference/operators/equality-operators.md#equality-operator-) operator requires a user-defined [!=](../language-reference/operators/equality-operators.md#inequality-operator-) operator, and vice versa. - The same applies also to a user-defined [true](../language-reference/operators/true-false-operators.md) operator and a user-defined [false](../language-reference/operators/true-false-operators.md) operator. - - The following sample generates CS0216: - -```csharp -// CS0216.cs -class MyClass -{ - public static bool operator == (MyClass MyIntLeft, MyClass MyIntRight) // CS0216 - { - return MyIntLeft == MyIntRight; - } - - // to resolve, uncomment the following operator definition - /* - public static bool operator != (MyClass MyIntLeft, MyClass MyIntRight) - { - return MyIntLeft != MyIntRight; - } - */ - - public override bool Equals (object obj) - { - return base.Equals (obj); - } - - public override int GetHashCode() - { - return base.GetHashCode(); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0217.md b/docs/csharp/misc/cs0217.md deleted file mode 100644 index e0bb38ad888cb..0000000000000 --- a/docs/csharp/misc/cs0217.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -description: "Compiler Error CS0217" -title: "Compiler Error CS0217" -ms.date: 07/20/2015 -f1_keywords: - - "CS0217" -helpviewer_keywords: - - "CS0217" -ms.assetid: ede61095-6e11-4f4a-8e7d-85e7a3f4fc3d ---- -# Compiler Error CS0217 - -In order to be applicable as a short circuit operator a user-defined logical operator ('operator') must have the same return type as the type of its 2 parameters. - - If you define an operator for a user-defined type, and then try to use the operator as a short-circuit operator, the user-defined operator must have parameters and return values of the same type. For more information about short-circuit operators, see [`&&` operator](../language-reference/operators/boolean-logical-operators.md#conditional-logical-and-operator-) and [`||` operator](../language-reference/operators/boolean-logical-operators.md#conditional-logical-or-operator-). For more information about user-defined short-circuit, or conditional, operators, see the [User-defined conditional logical operators](~/_csharpstandard/standard/expressions.md#12153-user-defined-conditional-logical-operators ) section of the [C# language specification](~/_csharpstandard/standard/README.md). - - The following sample generates CS0217: - -```csharp -// CS0217.cs -using System; - -public class MyClass -{ - public static bool operator true (MyClass f) - { - return false; - } - - public static bool operator false (MyClass f) - { - return false; - } - - public static implicit operator int(MyClass x) - { - return 0; - } - - public static int operator & (MyClass f1, MyClass f2) // CS0217 - // try the following line instead - // public static MyClass operator & (MyClass f1, MyClass f2) - { - return new MyClass(); - } - - public static void Main() - { - MyClass f = new MyClass(); - int i = f && f; - } -} -``` - -## See also - -- [Operator overloading](../language-reference/operators/operator-overloading.md) -- [true and false operators](../language-reference/operators/true-false-operators.md) diff --git a/docs/csharp/misc/cs0218.md b/docs/csharp/misc/cs0218.md deleted file mode 100644 index 072d5a070e252..0000000000000 --- a/docs/csharp/misc/cs0218.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -description: "Compiler Error CS0218" -title: "Compiler Error CS0218" -ms.date: 07/20/2015 -f1_keywords: - - "CS0218" -helpviewer_keywords: - - "CS0218" -ms.assetid: f675e06a-c55c-44a1-b5db-0df178fd8f79 ---- -# Compiler Error CS0218 - -The type ('type') must contain declarations of operator true and operator false - -If a user-defined type overloads the [& operator](../language-reference/operators/boolean-logical-operators.md#logical-and-operator-) or [| operator](../language-reference/operators/boolean-logical-operators.md#logical-or-operator-), it must also define [true and false](../language-reference/operators/true-false-operators.md) operators, in order to make short-circuiting [&& operator](../language-reference/operators/boolean-logical-operators.md#conditional-logical-and-operator-) or [|| operator](../language-reference/operators/boolean-logical-operators.md#conditional-logical-or-operator-) defined. - - The following sample generates CS0218: - -```csharp -// CS0218.cs -using System; -public class MyClass -{ - // uncomment these operator declarations to resolve this CS0218 - /* - public static bool operator true (MyClass f) - { - return false; - } - - public static bool operator false (MyClass f) - { - return false; - } - */ - - public static implicit operator int(MyClass x) - { - return 0; - } - - public static MyClass operator & (MyClass f1, MyClass f2) - { - return new MyClass(); - } - - public static void Main() - { - MyClass f = new MyClass(); - int i = f && f; // CS0218, requires operators true and false - } -} -``` - -## See also - -- [Operator overloading](../language-reference/operators/operator-overloading.md) diff --git a/docs/csharp/misc/cs0448.md b/docs/csharp/misc/cs0448.md deleted file mode 100644 index cae42f3dae3c8..0000000000000 --- a/docs/csharp/misc/cs0448.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -description: "Compiler Error CS0448" -title: "Compiler Error CS0448" -ms.date: 07/20/2015 -f1_keywords: - - "CS0448" -helpviewer_keywords: - - "CS0448" -ms.assetid: f577ab4c-1c8c-4a10-80a8-9ba9f66c3d25 ---- -# Compiler Error CS0448 - -The return type for ++ or -- operator must be the containing type or derived from the containing type - - When you override the `++` or `--` operators, they must return the same type as the containing type, or return a type that is derived from the containing type. - -## Example 1 - - The following sample generates CS0448. - -```csharp -// CS0448.cs -class C5 -{ - public static int operator ++(C5 c) { return null; } // CS0448 - public static C5 operator --(C5 c) { return null; } // OK - public static void Main() {} -} -``` - -## Example 2 - - The following sample generates CS0448. - -```csharp -// CS0448_b.cs -public struct S -{ - public static S? operator ++(S s) { return new S(); } // CS0448 - public static S? operator --(S s) { return new S(); } // CS0448 -} - -public struct T -{ -// OK - public static T operator --(T t) { return new T(); } - public static T operator ++(T t) { return new T(); } - - public static T? operator --(T? t) { return new T(); } - public static T? operator ++(T? t) { return new T(); } - - public static void Main() {} -} -``` diff --git a/docs/csharp/misc/cs0553.md b/docs/csharp/misc/cs0553.md deleted file mode 100644 index ccdbb0a1c50e9..0000000000000 --- a/docs/csharp/misc/cs0553.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: "Compiler Error CS0553" -title: "Compiler Error CS0553" -ms.date: 07/20/2015 -f1_keywords: - - "CS0553" -helpviewer_keywords: - - "CS0553" -ms.assetid: d2d6ddb1-9294-4e85-83d8-c35bd7a70f5b ---- -# Compiler Error CS0553 - -'conversion routine' : user defined conversion to/from base class - - User-defined conversions to values of a base class are not allowed; you do not need such an operator. - - The following sample generates CS0553: - -```csharp -// CS0553.cs -namespace x -{ - public class ii - { - } - - public class a : ii - { - // delete the conversion routine to resolve CS0553 - public static implicit operator ii(a aa) // CS0553 - { - return new ii(); - } - - public static void Main() - { - } - } -} -``` diff --git a/docs/csharp/misc/cs0554.md b/docs/csharp/misc/cs0554.md deleted file mode 100644 index 3c93d8e1f822f..0000000000000 --- a/docs/csharp/misc/cs0554.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -description: "Compiler Error CS0554" -title: "Compiler Error CS0554" -ms.date: 07/20/2015 -f1_keywords: - - "CS0554" -helpviewer_keywords: - - "CS0554" -ms.assetid: 884db4b2-3a69-4434-9a25-526f596e03c8 ---- -# Compiler Error CS0554 - -'conversion routine' : user defined conversion to/from derived class - - User-defined conversions to values of a derived class are not allowed; you do not need such an operator. - - See chapter 6 in the C# language specification for more information on user-defined conversions. - - The following sample generates CS0554: - -```csharp -// CS0554.cs -namespace x -{ - public class ii - { - // delete the conversion routine to resolve CS0554 - public static implicit operator ii(a aa) // CS0554 - { - return new ii(); - } - } - - public class a : ii - { - public static void Main() - { - } - } -} -``` diff --git a/docs/csharp/misc/cs0555.md b/docs/csharp/misc/cs0555.md deleted file mode 100644 index 09d40004014b6..0000000000000 --- a/docs/csharp/misc/cs0555.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: "Compiler Error CS0555" -title: "Compiler Error CS0555" -ms.date: 07/20/2015 -f1_keywords: - - "CS0555" -helpviewer_keywords: - - "CS0555" -ms.assetid: e4b2f890-98b4-4578-b1de-ebaafc8b3da2 ---- - -# Compiler Error CS0555 - -User-defined operator cannot take an object of the enclosing type and convert to an object of the enclosing type - -User-defined conversions to values of the enclosing class are not allowed; you do not need such an operator. - -The following sample generates CS0555: - -```csharp -// CS0555.cs -public class MyClass -{ - // delete the following operator to resolve this CS0555 - public static implicit operator MyClass(MyClass aa) // CS0555 - { - return new MyClass(); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0556.md b/docs/csharp/misc/cs0556.md deleted file mode 100644 index efb706426cb60..0000000000000 --- a/docs/csharp/misc/cs0556.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -description: "Compiler Error CS0556" -title: "Compiler Error CS0556" -ms.date: 07/20/2015 -f1_keywords: - - "CS0556" -helpviewer_keywords: - - "CS0556" -ms.assetid: e2430c6e-784f-4ab2-88b9-f660d956e9e8 ---- -# Compiler Error CS0556 - -User-defined conversion must convert to or from the enclosing type - - A user-defined conversion routine must convert to or from the class that contains the routine. - - The following sample generates CS0556: - -```csharp -// CS0556.cs -namespace x -{ - public class ii - { - public class iii - { - public static implicit operator int(byte aa) // CS0556 - // try the following line instead - // public static implicit operator int(iii aa) - { - return 0; - } - } - - public static void Main() - { - } - } -} -``` diff --git a/docs/csharp/misc/cs0557.md b/docs/csharp/misc/cs0557.md deleted file mode 100644 index dc37957c13f79..0000000000000 --- a/docs/csharp/misc/cs0557.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: "Compiler Error CS0557" -title: "Compiler Error CS0557" -ms.date: 07/20/2015 -f1_keywords: - - "CS0557" -helpviewer_keywords: - - "CS0557" -ms.assetid: beca353e-4fea-4e4f-a48a-eddeebb153bb ---- -# Compiler Error CS0557 - -Duplicate user-defined conversion in type 'class' - - Duplicate conversion routines are not allowed in a class. - - The following example generates CS0557: - -```csharp -// CS0557.cs -namespace x -{ - public class ii - { - public class iii - { - public static implicit operator int(iii aa) - { - return 0; - } - - // CS0557, delete duplicate - public static explicit operator int(iii aa) - { - return 0; - } - } - - public static void Main() - { - } - } -} -``` diff --git a/docs/csharp/misc/cs0558.md b/docs/csharp/misc/cs0558.md deleted file mode 100644 index e19c5587adfcd..0000000000000 --- a/docs/csharp/misc/cs0558.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -description: "Compiler Error CS0558" -title: "Compiler Error CS0558" -ms.date: 07/20/2015 -f1_keywords: - - "CS0558" -helpviewer_keywords: - - "CS0558" -ms.assetid: af63b9ba-2790-4362-a49d-b69a5292a555 ---- -# Compiler Error CS0558 - -User-defined operator 'operator' must be declared static and public - - Both the **static** and **public** access [modifiers](../language-reference/keywords/index.md) must be specified on user-defined operators. - - The following sample generates CS0558: - -```csharp -// CS0558.cs -namespace x -{ - public class ii - { - public class iii - { - static implicit operator int(iii aa) // CS0558, add public - { - return 0; - } - } - - public static void Main() - { - } - } -} -``` diff --git a/docs/csharp/misc/cs0559.md b/docs/csharp/misc/cs0559.md deleted file mode 100644 index 47f6e8b0dc023..0000000000000 --- a/docs/csharp/misc/cs0559.md +++ /dev/null @@ -1,67 +0,0 @@ ---- -description: "Compiler Error CS0559" -title: "Compiler Error CS0559" -ms.date: 07/20/2015 -f1_keywords: - - "CS0559" -helpviewer_keywords: - - "CS0559" -ms.assetid: 37122001-8a55-4cf5-873b-68997e196893 ---- -# Compiler Error CS0559 - -The parameter type for ++ or -- operator must be the containing type - - The method declaration for an operator overload must follow certain guidelines. For the ++ and -- operators, it is required that the parameter be of the same type as the type in which the operator is being overloaded. - -## Example 1 - - The following sample generates CS0559: - -```csharp -// CS0559.cs -// compile with: /target:library -public class iii -{ - public static implicit operator int(iii x) - { - return 0; - } - - public static implicit operator iii(int x) - { - return null; - } - - public static int operator ++(int aa) // CS0559 - // try the following line instead - // public static iii operator ++(iii aa) - { - return (iii)0; - } -} -``` - -## Example 2 - - The following sample generates CS0559. - -```csharp -// CS0559_b.cs -// compile with: /target:library -public struct S -{ - public static S operator ++(S? s) { return new S(); } // CS0559 - public static S operator --(S? s) { return new S(); } // CS0559 -} - -public struct T -{ -// OK - public static T operator --(T t) { return new T(); } - public static T operator ++(T t) { return new T(); } - - public static T? operator --(T? t) { return new T(); } - public static T? operator ++(T? t) { return new T(); } -} -``` diff --git a/docs/csharp/misc/cs0562.md b/docs/csharp/misc/cs0562.md deleted file mode 100644 index 377f37487725a..0000000000000 --- a/docs/csharp/misc/cs0562.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: "Compiler Error CS0562" -title: "Compiler Error CS0562" -ms.date: 07/20/2015 -f1_keywords: - - "CS0562" -helpviewer_keywords: - - "CS0562" -ms.assetid: dceecce5-90ce-4554-820c-f4c06b2b8258 ---- -# Compiler Error CS0562 - -The parameter of a unary operator must be the containing type - -The method declaration for an operator overload must follow certain guidelines. For more information, see [Operator overloading](../language-reference/operators/operator-overloading.md). - -The following sample generates CS0562: - -```csharp -// CS0562.cs -public class iii -{ - public static implicit operator int(iii x) - { - return 0; - } - - public static implicit operator iii(int x) - { - return null; - } - - public static iii operator +(int aa) // CS0562 - // try the following line instead - // public static iii operator +(iii aa) - { - return (iii)0; - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0564.md b/docs/csharp/misc/cs0564.md deleted file mode 100644 index 37b1f083fa291..0000000000000 --- a/docs/csharp/misc/cs0564.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: "Compiler Error CS0564" -title: "Compiler Error CS0564" -ms.date: 07/20/2015 -f1_keywords: - - "CS0564" -helpviewer_keywords: - - "CS0564" -ms.assetid: 4c152e10-eb22-4437-a85f-1599c76470e0 ---- -# Compiler Error CS0564 - -The first operand of an overloaded shift operator must have the same type as the containing type, and the type of the second operand must be int - - You attempted to overload a shift operator (<\< or >>) with incorrectly typed operands. The first operand must be the type and the second operand must be of the type `int`. - - The following sample generates CS0564: - -```csharp -// CS0564.cs -using System; -class C -{ - public static int operator << (C c1, C c2) // CS0564 -// To correct, change second operand to int, like so: -// public static int operator << (C c1, int c2) - { - return 0; - } - static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0567.md b/docs/csharp/misc/cs0567.md deleted file mode 100644 index af5ac3da40106..0000000000000 --- a/docs/csharp/misc/cs0567.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -description: "Compiler Error CS0567" -title: "Compiler Error CS0567" -ms.date: 07/20/2015 -f1_keywords: - - "CS0567" -helpviewer_keywords: - - "CS0567" -ms.assetid: 90aefbf9-d216-4eb4-96d4-44926fa23b1e ---- -# Compiler Error CS0567 - -Interfaces cannot contain operators - - Operators are not permitted in [interface](../language-reference/keywords/interface.md) definitions. - - The following sample generates CS0567: - -```csharp -// CS0567.cs -interface IA -{ - int operator +(int aa, int bb); // CS0567 -} - -class Sample -{ - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0590.md b/docs/csharp/misc/cs0590.md deleted file mode 100644 index 538270c39c124..0000000000000 --- a/docs/csharp/misc/cs0590.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -description: "Compiler Error CS0590" -title: "Compiler Error CS0590" -ms.date: 07/20/2015 -f1_keywords: - - "CS0590" -helpviewer_keywords: - - "CS0590" -ms.assetid: 6df9461f-2de6-4032-b18f-96121db1e4af ---- -# Compiler Error CS0590 - -User-defined operators cannot return void - - The purpose of a user-defined operator is to return an object. - - The following sample generates CS0590: - -```csharp -// CS0590.cs -namespace x -{ - public class a - { - public static void operator+(a A1, a A2) // CS0590 - { - } - - // try the following user-defined operator - /* - public static a operator+(a A1, a A2) - { - return A2; - } - */ - - public static int Main() - { - return 1; - } - } -} -``` diff --git a/docs/csharp/misc/cs0660.md b/docs/csharp/misc/cs0660.md deleted file mode 100644 index a82634a5392cc..0000000000000 --- a/docs/csharp/misc/cs0660.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -description: "Compiler Warning (level 3) CS0660" -title: "Compiler Warning (level 3) CS0660" -ms.date: 07/20/2015 -f1_keywords: - - "CS0660" -helpviewer_keywords: - - "CS0660" -ms.assetid: 2f77b45b-c5c6-46af-abe9-002e67887896 ---- -# Compiler Warning (level 3) CS0660 - -'class' defines operator == or operator != but does not override Object.Equals(object o) - -The compiler detected the user-defined equality or inequality operator, but no override for the method. A user-defined equality or inequality operator implies that you also want to override the method. For more information, see [How to define value equality for a type](../programming-guide/statements-expressions-operators/how-to-define-value-equality-for-a-type.md). - -The following sample generates CS0660: - -```csharp -// CS0660.cs -// compile with: /W:3 /warnaserror -class Test // CS0660 -{ - public static bool operator == (object o, Test t) - { - return true; - } - - // uncomment the Equals function to resolve - // public override bool Equals(object o) - // { - // return true; - // } - - public override int GetHashCode() - { - return 0; - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0661.md b/docs/csharp/misc/cs0661.md deleted file mode 100644 index 12ca927c95263..0000000000000 --- a/docs/csharp/misc/cs0661.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -description: "Compiler Warning (level 3) CS0661" -title: "Compiler Warning (level 3) CS0661" -ms.date: 07/20/2015 -f1_keywords: - - "CS0661" -helpviewer_keywords: - - "CS0661" -ms.assetid: c218665e-5947-40bb-b633-d268483e6522 ---- -# Compiler Warning (level 3) CS0661 - -'class' defines operator == or operator != but does not override Object.GetHashCode() - - The compiler detected the user-defined equality or inequality operator, but no override for the **GetHashCode** function. A user-defined equality or inequality operator implies that you also want to override the **GetHashCode** function. - - The following sample generates CS0661: - -```csharp -// CS0661.cs -// compile with: /W:3 -class Test // CS0661 -{ - public static bool operator == (object o, Test t) - { - return true; - } - - public static bool operator != (object o, Test t) - { - return true; - } - - public override bool Equals(object o) - { - return true; - } - - // uncomment the GetHashCode function to resolve - // public override int GetHashCode() - // { - // return 0; - // } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs0715.md b/docs/csharp/misc/cs0715.md deleted file mode 100644 index fd403e34f1d9b..0000000000000 --- a/docs/csharp/misc/cs0715.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -description: "Compiler Error CS0715" -title: "Compiler Error CS0715" -ms.date: 07/20/2015 -f1_keywords: - - "CS0715" -helpviewer_keywords: - - "CS0715" -ms.assetid: e3cd1e46-ccfa-4678-a2ed-69245f3558ba ---- -# Compiler Error CS0715 - -'static class' : static classes cannot contain user defined operators - - User defined operators operate on instances of classes. Static classes cannot be instantiated, so it is not possible to create instances for operators to act upon. Hence, user defined operators are not allowed for static classes. - - The following sample generates CS0715: - -```csharp -// CS0715.cs -public static class C -{ - public static C operator+(C c) // CS0715 - { - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs1037.md b/docs/csharp/misc/cs1037.md deleted file mode 100644 index 012459c724563..0000000000000 --- a/docs/csharp/misc/cs1037.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -description: "Compiler Error CS1037" -title: "Compiler Error CS1037" -ms.date: 07/20/2015 -f1_keywords: - - "CS1037" -helpviewer_keywords: - - "CS1037" -ms.assetid: 22e16a58-e77b-41cf-b4b5-b2603bb819c6 ---- -# Compiler Error CS1037 - -Overloadable operator expected - - When specifying a comment with [**DocumentationFile**](../language-reference/compiler-options/output.md#documentationfile), the compiler encountered an invalid link. diff --git a/docs/csharp/misc/cs1553.md b/docs/csharp/misc/cs1553.md deleted file mode 100644 index 4ebafaa7b329a..0000000000000 --- a/docs/csharp/misc/cs1553.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: "Compiler Error CS1553" -title: "Compiler Error CS1553" -ms.date: 07/20/2015 -f1_keywords: - - "CS1553" -helpviewer_keywords: - - "CS1553" -ms.assetid: aec64251-b4ac-45c0-b143-7ebda138af6e ---- -# Compiler Error CS1553 - -Declaration is not valid; use 'modifier operator \ (...' instead - -The return type for a [conversion operator](../language-reference/operators/user-defined-conversion-operators.md) must immediately precede the parameter list, and *modifier* is either `implicit` or `explicit`. - -The following sample generates CS1553: - -```csharp -// CS1553.cs -class MyClass -{ - public static int implicit operator (MyClass f) // CS1553 - // try the following line instead - // public static implicit operator int (MyClass f) - { - return 6; - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/cs1554.md b/docs/csharp/misc/cs1554.md deleted file mode 100644 index 352c2308f5c3f..0000000000000 --- a/docs/csharp/misc/cs1554.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -description: "Compiler Error CS1554" -title: "Compiler Error CS1554" -ms.date: 07/20/2015 -f1_keywords: - - "CS1554" -helpviewer_keywords: - - "CS1554" -ms.assetid: 81e8d4ac-cdbf-4b75-8932-0bc271a8405c ---- -# Compiler Error CS1554 - -Declaration is not valid; use '\ operator op (...' instead - -The return type of an [overloaded operator](../language-reference/operators/operator-overloading.md) must appear before the `operator` keyword. - -The following sample generates CS1554: - -```csharp -// CS1554.cs -class MyClass -{ - public static operator ++ MyClass (MyClass f) // CS1554 - // try the following line instead - // public static MyClass operator ++ (MyClass f) - { - return new MyClass (); - } - - public static void Main() - { - } -} -``` diff --git a/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md b/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md index 78ddbf37eee9d..c8f5d9aece3ea 100644 --- a/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md +++ b/docs/csharp/misc/sorry-we-don-t-have-specifics-on-this-csharp-error.md @@ -513,9 +513,6 @@ f1_keywords: - "CS9020" - "CS9021" - "CS9022" - - "CS9023" - - "CS9024" - - "CS9025" - "CS9026" - "CS9027" - "CS9029" @@ -598,12 +595,6 @@ f1_keywords: # C# 14 errors begin here ## using `extension` as type name. (valid identifiers). - "CS9306" -## User defined operators - - "CS9308" - - "CS9310" - - "CS9311" - - "CS9312" - - "CS9313" ## More extension errors - "CS9316" - "CS9317" diff --git a/docs/csharp/programming-guide/classes-and-structs/access-modifiers.md b/docs/csharp/programming-guide/classes-and-structs/access-modifiers.md index ed954559d7883..de8f7d54f7936 100644 --- a/docs/csharp/programming-guide/classes-and-structs/access-modifiers.md +++ b/docs/csharp/programming-guide/classes-and-structs/access-modifiers.md @@ -1,7 +1,7 @@ --- title: "Access Modifiers" description: All types and type members in C# have an accessibility level that controls whether they can be used from other code. Review this list of access modifiers. -ms.date: 08/20/2024 +ms.date: 10/10/2025 helpviewer_keywords: - "C# Language, access modifiers" - "access modifiers [C#], about" @@ -41,7 +41,7 @@ Multiple declarations of a [partial class or partial member](./partial-classes-a ## Class and struct accessibility -Classes and structs declared directly within a namespace (aren't nested within other classes or structs) can have `public`, `internal` or `file` access. `internal` is the default if no access modifier is specified. +Classes and structs declared directly within a namespace (aren't nested within other classes or structs) can have `public`, `internal`, or `file` access. `internal` is the default if no access modifier is specified. Struct members, including nested classes and structs, can be declared `public`, `internal`, or `private`. Class members, including nested classes and structs, can be `public`, `protected internal`, `protected`, `internal`, `private protected`, or `private`. Class and struct members, including nested classes and structs, have `private` access by default. diff --git a/docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md b/docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md index 9316a2dc5187e..12a18d0101d1e 100644 --- a/docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md +++ b/docs/csharp/programming-guide/classes-and-structs/named-and-optional-arguments.md @@ -1,7 +1,7 @@ --- title: "Named and Optional Arguments" description: Named arguments in C# specify arguments by name, not position. Optional arguments can be omitted. -ms.date: 03/14/2024 +ms.date: 10/10/2025 f1_keywords: - "namedParameter_CSharpKeyword" - "optionalParameter_CSharpKeyword" @@ -26,7 +26,7 @@ Named and optional parameters enable you to supply arguments for selected parame ## Named arguments -Named arguments free you from matching the order of arguments to the order of parameters in the parameter lists of called methods. The argument for each parameter can be specified by parameter name. For example, a function that prints order details (such as seller name, order number, and product name) can be called by sending arguments by position, in the order defined by the function. +Named arguments free you from matching the order of arguments to the order of parameters in the parameter lists of called methods. The argument for each parameter is specified by parameter name. For example, a function that prints order details (such as seller name, order number, and product name) is called by sending arguments by position, in the order defined by the function. ```csharp PrintOrderDetails("Gift Shop", 31, "Red Mug"); @@ -39,7 +39,7 @@ PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop") PrintOrderDetails(productName: "Red Mug", sellerName: "Gift Shop", orderNum: 31); ``` -Named arguments also improve the readability of your code by identifying what each argument represents. In the example method below, the `sellerName` can't be null or white space. As both `sellerName` and `productName` are string types, instead of sending arguments by position, it makes sense to use named arguments to disambiguate the two and reduce confusion for anyone reading the code. +Named arguments also improve the readability of your code by identifying what each argument represents. In the following example method, the `sellerName` can't be null or white space. As both `sellerName` and `productName` are string types, instead of sending arguments by position, it makes sense to use named arguments to disambiguate the two and reduce confusion for anyone reading the code. Named arguments, when used with positional arguments, are valid as long as @@ -49,7 +49,7 @@ Named arguments, when used with positional arguments, are valid as long as PrintOrderDetails("Gift Shop", 31, productName: "Red Mug"); ``` -- they're used in the correct position. In the example below, the parameter `orderNum` is in the correct position but isn't explicitly named. +- they're used in the correct position. In the following example, the parameter `orderNum` is in the correct position but isn't explicitly named. ```csharp PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug"); @@ -64,13 +64,13 @@ PrintOrderDetails(productName: "Red Mug", 31, "Gift Shop"); ### Example -The following code implements the examples from this section along with some additional ones. +The following code implements the examples from this section along with some extra ones. :::code language="csharp" source="./snippets/NamedAndOptional/program.cs" id="Snippet1"::: ## Optional arguments -The definition of a method, constructor, indexer, or delegate can specify its parameters are required or optional. Any call must provide arguments for all required parameters, but can omit arguments for optional parameters. A nullable reference type (`T?`) allows arguments to be explicitly `null` but does not inherently make a parameter optional. +The definition of a method, constructor, indexer, or delegate can specify its parameters are required or optional. Any call must provide arguments for all required parameters, but can omit arguments for optional parameters. A nullable reference type (`T?`) allows arguments to be explicitly `null` but doesn't inherently make a parameter optional. Each optional parameter has a default value as part of its definition. If no argument is sent for that parameter, the default value is used. A default value must be one of the following types of expressions: @@ -78,7 +78,7 @@ Each optional parameter has a default value as part of its definition. If no arg - an expression of the form `new ValType()`, where `ValType` is a value type, such as an [enum](../../language-reference/builtin-types/enum.md) or a [struct](../../language-reference/builtin-types/struct.md); - an expression of the form [default(ValType)](../../language-reference/operators/default.md), where `ValType` is a value type. -Optional parameters are defined at the end of the parameter list, after any required parameters. The caller must provide arguments for all required parameters and any optional parameters preceding those it specifies. Comma-separated gaps in the argument list aren't supported.For example, in the following code, instance method `ExampleMethod` is defined with one required and two optional parameters. +Optional parameters are defined at the end of the parameter list, after any required parameters. The caller must provide arguments for all required parameters and any optional parameters preceding those it specifies. Comma-separated gaps in the argument list aren't supported. For example, in the following code, instance method `ExampleMethod` is defined with one required and two optional parameters. :::code language="csharp" source="./snippets/NamedAndOptional/optional.cs" id="Snippet15"::: @@ -99,7 +99,7 @@ IntelliSense uses brackets to indicate optional parameters, as shown in the foll ![Screenshot showing IntelliSense quick info for the ExampleMethod method.](./media/named-and-optional-arguments/optional-examplemethod-parameters.png) > [!NOTE] -> You can also declare optional parameters by using the .NET class. `OptionalAttribute` parameters do not require a default value. However, if a default value is desired, take a look at class. +> You can also declare optional parameters by using the .NET class. `OptionalAttribute` parameters don't require a default value. However, if a default value is desired, take a look at class. ### Example @@ -113,7 +113,7 @@ The preceding code illustrates several cases where optional parameters are used [Caller information attributes](../../language-reference/attributes/caller-information.md), such as , , , and , are used to obtain information about the caller to a method. These attributes are especially useful when you're debugging or when you need to log information about method calls. -These attributes are optional parameters with default values provided by the compiler. The caller should not explicitly provide a value for these parameters. +These attributes are optional parameters with default values provided by the compiler. The caller shouldn't explicitly provide a value for these parameters. ## COM interfaces diff --git a/docs/csharp/programming-guide/classes-and-structs/object-and-collection-initializers.md b/docs/csharp/programming-guide/classes-and-structs/object-and-collection-initializers.md index 6b86a1001474f..ed0b8890f3cb8 100644 --- a/docs/csharp/programming-guide/classes-and-structs/object-and-collection-initializers.md +++ b/docs/csharp/programming-guide/classes-and-structs/object-and-collection-initializers.md @@ -1,7 +1,7 @@ --- title: "Object and Collection Initializers" description: Object initializers in C# assign values to accessible fields or properties of an object at creation after invoking a constructor. -ms.date: 05/17/2024 +ms.date: 10/13/2025 helpviewer_keywords: - "object initializers [C#]" - "collection initializers [C#]" @@ -19,7 +19,7 @@ Object initializers let you assign values to any accessible fields or properties The object initializers syntax allows you to create an instance, and after that it assigns the newly created object, with its assigned properties, to the variable in the assignment. -Starting with nested object properties, you can use object initializer syntax without the `new` keyword. This syntax, `Property = { ... }`, allows you to initialize members of existing nested objects, which is particularly useful with read-only properties. For more details, see [Object Initializers with class-typed properties](#object-initializers-with-class-typed-properties). +Starting with nested object properties, you can use object initializer syntax without the `new` keyword. This syntax, `Property = { ... }`, allows you to initialize members of existing nested objects, which is useful with read-only properties. For more information, see [Object Initializers with class-typed properties](#object-initializers-with-class-typed-properties). Object initializers can set indexers, in addition to assigning fields and properties. Consider this basic `Matrix` class: @@ -54,7 +54,7 @@ public string this[char c, int i] { set { ... }; } ## Object Initializers with anonymous types -Although object initializers can be used in any context, they're especially useful in LINQ query expressions. Query expressions make frequent use of [anonymous types](../../fundamentals/types/anonymous-types.md), which can only be initialized by using an object initializer, as shown in the following declaration. +Although object initializers can be used in any context, they're especially useful in Language-Integrated Query (LINQ) expressions. Query expressions make frequent use of [anonymous types](../../fundamentals/types/anonymous-types.md), which can only be initialized by using an object initializer, as shown in the following declaration. ```csharp var pet = new { Age = 10, Name = "Fluffy" }; @@ -139,13 +139,13 @@ These syntaxes behave differently. The following example demonstrates both appro - **Without `new` keyword** (`ClassB = { BI = 100003 }`): This syntax modifies the existing instance of the property that was created during object construction. It calls member initializers on the existing object. -- **With `new` keyword** (`ClassB = new() { BI = 100003 }`): This syntax creates a completely new instance and assigns it to the property, replacing any existing instance. +- **With `new` keyword** (`ClassB = new() { BI = 100003 }`): This syntax creates a new instance and assigns it to the property, replacing any existing instance. -The initializer without `new` reuses the current instance. In the example above, ClassB's values are: `100003` (new value assigned), `true` (kept from EmbeddedClassTypeA's initialization), `BBBabc` (unchanged default from EmbeddedClassTypeB). +The initializer without `new` reuses the current instance. In the previous example, ClassB's values are: `100003` (new value assigned), `true` (kept from EmbeddedClassTypeA's initialization), `BBBabc` (unchanged default from EmbeddedClassTypeB). ### Object initializers without `new` for read-only properties -The syntax without `new` is particularly useful with read-only properties, where you can't assign a new instance but can still initialize the existing instance's members: +The syntax without `new` is useful with read-only properties, where you can't assign a new instance but can still initialize the existing instance's members: :::code language="csharp" source="./snippets/object-collection-initializers/ObjectInitializerWithoutNew.cs" id="ReadOnlyPropertyExample"::: diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/app.config b/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/app.config deleted file mode 100644 index 3e0e37cfc8c7a..0000000000000 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/namedandoptionalsnippets.csproj b/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/namedandoptionalsnippets.csproj index ae9dcbfe6c5a7..05ebaeab7a292 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/namedandoptionalsnippets.csproj +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/namedandoptionalsnippets.csproj @@ -1,94 +1,16 @@ - - + + - Debug - AnyCPU - 10.0.20319 - 2.0 - {595F32EC-B330-4DA1-9D79-9E5E1EA6DDAB} Exe - Properties - NamedAndOptionalSnippets - NamedAndOptionalSnippets - v4.8 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - + net8.0 + enable + enable OptionalNamespace.OptionalExample + - - - - - - - - - - - {2DF8D04C-5BFA-101B-BDE5-00AA0044DE52} - 2 - 8 - 0 - primary - False - True - - - {00020813-0000-0000-C000-000000000046} - 1 - 9 - 0 - primary - False - True - - - {00020905-0000-0000-C000-000000000046} - 8 - 7 - 0 - primary - False - True - - - {0002E157-0000-0000-C000-000000000046} - 5 - 3 - 0 - primary - False - True - + + - - + \ No newline at end of file diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/properties/assemblyinfo.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/properties/assemblyinfo.cs deleted file mode 100644 index 08d25b77fa98c..0000000000000 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/NamedAndOptional/properties/assemblyinfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NamedAndOptionalSnippets")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft Corp.")] -[assembly: AssemblyProduct("NamedAndOptionalSnippets")] -[assembly: AssemblyCopyright("Copyright © Microsoft Corp. 2009")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6803107a-bd5f-4a70-834b-0988088f89bb")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/object-collection-initializers/BasicObjectInitializers.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/object-collection-initializers/BasicObjectInitializers.cs index 51b5b2f63701c..cacb69c078b23 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/object-collection-initializers/BasicObjectInitializers.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/object-collection-initializers/BasicObjectInitializers.cs @@ -1,5 +1,4 @@ - -namespace object_collection_initializers; +namespace object_collection_initializers; public class BasicObjectInitializers { @@ -81,12 +80,12 @@ from p in products // // - List cats = new List - { - new Cat{ Name = "Sylvester", Age=8 }, - new Cat{ Name = "Whiskers", Age=2 }, - new Cat{ Name = "Sasha", Age=14 } - }; + List cats = + [ + new Cat { Name = "Sylvester", Age = 8 }, + new Cat { Name = "Whiskers", Age = 2 }, + new Cat { Name = "Sasha", Age = 14 } + ]; // // @@ -176,12 +175,12 @@ public static void Main() Cat cat = new Cat { Age = 10, Name = "Fluffy" }; Cat sameCat = new Cat("Fluffy"){ Age = 10 }; - List cats = new List - { + List cats = + [ new Cat { Name = "Sylvester", Age = 8 }, new Cat { Name = "Whiskers", Age = 2 }, new Cat { Name = "Sasha", Age = 14 } - }; + ]; List moreCats = new List { @@ -222,7 +221,7 @@ public class FullExample { class FormattedAddresses : IEnumerable { - private List internalList = new List(); + private List internalList = new(); public IEnumerator GetEnumerator() => internalList.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => internalList.GetEnumerator(); @@ -292,7 +291,7 @@ public void Add(TKey key, IEnumerable values) { if (!internalDictionary.TryGetValue(key, out List? storedValues)) { - internalDictionary.Add(key, storedValues = new List()); + internalDictionary.Add(key, storedValues = new()); } storedValues.AddRange(values); } diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/static-classes-and-static-class-members/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/static-classes-and-static-class-members/Program.cs index 92ca7636bdfcc..483f8abc8fa18 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/static-classes-and-static-class-members/Program.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/static-classes-and-static-class-members/Program.cs @@ -1,27 +1,11 @@ // public static class TemperatureConverter { - public static double CelsiusToFahrenheit(string temperatureCelsius) - { - // Convert argument to double for calculations. - double celsius = Double.Parse(temperatureCelsius); - - // Convert Celsius to Fahrenheit. - double fahrenheit = (celsius * 9 / 5) + 32; - - return fahrenheit; - } - - public static double FahrenheitToCelsius(string temperatureFahrenheit) - { - // Convert argument to double for calculations. - double fahrenheit = Double.Parse(temperatureFahrenheit); + public static double CelsiusToFahrenheit(string temperatureCelsius) => + double.Parse(temperatureCelsius) * 9 / 5 + 32; - // Convert Fahrenheit to Celsius. - double celsius = (fahrenheit - 32) * 5 / 9; - - return celsius; - } + public static double FahrenheitToCelsius(string temperatureFahrenheit) => + (double.Parse(temperatureFahrenheit) - 32) * 5 / 9; } class TestTemperatureConverter @@ -34,20 +18,19 @@ static void Main() Console.Write(":"); string? selection = Console.ReadLine(); - double F, C = 0; switch (selection) { case "1": Console.Write("Please enter the Celsius temperature: "); - F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0"); - Console.WriteLine($"Temperature in Fahrenheit: {F:F2}"); + var f = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine() ?? "0"); + Console.WriteLine($"Temperature in Fahrenheit: {f:F2}"); break; case "2": Console.Write("Please enter the Fahrenheit temperature: "); - C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0"); - Console.WriteLine($"Temperature in Celsius: {C:F2}"); + var c = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine() ?? "0"); + Console.WriteLine($"Temperature in Celsius: {c:F2}"); break; default: @@ -78,13 +61,7 @@ public class Automobile { public static int NumberOfWheels = 4; - public static int SizeOfGasTank - { - get - { - return 15; - } - } + public static int SizeOfGasTank => 15; public static void Drive() { } @@ -100,7 +77,7 @@ void Test() { // Automobile.Drive(); - int i = Automobile.NumberOfWheels; + var i = Automobile.NumberOfWheels; // } } diff --git a/docs/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members.md b/docs/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members.md index f898e3e72b42b..d0678cf53c8bf 100644 --- a/docs/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members.md +++ b/docs/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members.md @@ -1,7 +1,7 @@ --- title: "Static Classes and Static Class Members" description: Static classes can't be instantiated in C#. You access the members of a static class by using the class name itself. -ms.date: 03/15/2024 +ms.date: 10/13/2025 helpviewer_keywords: - "C# language, static members" - "static members [C#]" @@ -18,7 +18,7 @@ A [static](../../language-reference/keywords/static.md) class is basically the s UtilityClass.MethodA(); ``` - A static class can be used as a convenient container for sets of methods that just operate on input parameters and don't have to get or set any internal instance fields. For example, in the .NET Class Library, the static class contains methods that perform mathematical operations, without any requirement to store or retrieve data that is unique to a particular instance of the class. That is, you apply the members of the class by specifying the class name and the method name, as shown in the following example. + A static class can be used as a convenient container for sets of methods that just operate on input parameters and don't have to get or set any internal instance fields. For example, in the .NET Class Library, the static class contains methods that perform mathematical operations, without any requirement to store or retrieve data that's unique to a particular instance of the class. That is, you apply the members of the class by specifying the class name and the method name, as shown in the following example. ```csharp double dub = -3.14; diff --git a/docs/csharp/programming-guide/concepts/index.md b/docs/csharp/programming-guide/concepts/index.md index 2d8f7f3a981d4..30e0d01a9f9e0 100644 --- a/docs/csharp/programming-guide/concepts/index.md +++ b/docs/csharp/programming-guide/concepts/index.md @@ -1,7 +1,7 @@ --- title: "Programming Concepts" description: Use the resources in this section to understand programming concepts in the C# language, including object-oriented programming. -ms.date: 04/22/2024 +ms.date: 10/10/2025 --- # Programming concepts (C#) diff --git a/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md b/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md index 57336cebcb272..0adad6ba44ade 100644 --- a/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md +++ b/docs/csharp/programming-guide/generics/constraints-on-type-parameters.md @@ -1,7 +1,7 @@ --- title: "Constraints on type parameters" description: Learn about constraints on type parameters. Constraints tell the compiler what capabilities a type argument must have. -ms.date: 07/26/2024 +ms.date: 10/10/2025 f1_keywords: - "defaultconstraint_CSharpKeyword" - "notnull_CSharpKeyword" @@ -117,7 +117,7 @@ The `unmanaged` constraint implies the `struct` constraint and can't be combined ## Delegate constraints -You can use or as a base class constraint. The CLR always allowed this constraint, but the C# language disallowed it. The `System.Delegate` constraint enables you to write code that works with delegates in a type-safe manner. The following code defines an extension method that combines two delegates provided they're the same type: +You can use or as a base class constraint. The Common Language Runtime (CLR) always allowed this constraint, but the C# language disallowed it. The `System.Delegate` constraint enables you to write code that works with delegates in a type-safe manner. The following code defines an extension method that combines two delegates provided they're the same type: :::code language="csharp" source="./snippets/GenericWhereConstraints.cs" id="Snippet16"::: diff --git a/docs/csharp/programming-guide/generics/snippets/GenericWhereConstraints.cs b/docs/csharp/programming-guide/generics/snippets/GenericWhereConstraints.cs index a2b9c907e1b7f..06d9d7ce50141 100644 --- a/docs/csharp/programming-guide/generics/snippets/GenericWhereConstraints.cs +++ b/docs/csharp/programming-guide/generics/snippets/GenericWhereConstraints.cs @@ -6,7 +6,7 @@ namespace Generics public class AGenericClass where T : IComparable { } // - // + // public class UsingEnum where T : System.Enum { } public class UsingDelegate where T : System.Delegate { } @@ -28,19 +28,16 @@ class UnManagedWrapper // // -#nullable enable class NotNullContainer where T : notnull { } -#nullable restore // // public class MyGenericClass where T : IComparable, new() { - // The following line is not possible without new() constraint: - T item = new T(); + T item = new(); } // @@ -90,7 +87,7 @@ private class Node public void AddHead(T t) { - Node n = new Node(t) { Next = head }; + Node n = new(t) { Next = head }; head = n; } @@ -98,7 +95,7 @@ public IEnumerator GetEnumerator() { Node? current = head; - while (current != null) + while (current is not null) { yield return current.Data; current = current.Next; @@ -108,22 +105,20 @@ public IEnumerator GetEnumerator() public T? FindFirstOccurrence(string s) { Node? current = head; - T? t = null; - while (current != null) + while (current is not null) { //The constraint enables access to the Name property. if (current.Data.Name == s) { - t = current.Data; - break; + return current.Data; } else { current = current.Next; } } - return t; + return null; } } // @@ -135,11 +130,9 @@ public interface IEmployee // class EmployeeList where T : notnull, Employee, IComparable, new() { - // ... public void AddDefault() { - T t = new T(); - // ... + T t = new(); } } // @@ -173,8 +166,8 @@ public static class UnmanagedExtensions unsafe public static byte[] ToByteArray(this T argument) where T : unmanaged { var size = sizeof(T); - var result = new Byte[size]; - Byte* p = (byte*)&argument; + var result = new byte[size]; + byte* p = (byte*)&argument; for (var i = 0; i < size; i++) result[i] = *p++; return result; @@ -212,15 +205,15 @@ public static void Examples() // public static void OpEqualsTest(T s, T t) where T : class { - System.Console.WriteLine(s == t); + Console.WriteLine(s == t); } private static void TestStringEquality() { string s1 = "target"; - System.Text.StringBuilder sb = new System.Text.StringBuilder("target"); + System.Text.StringBuilder sb = new("target"); string s2 = sb.ToString(); - OpEqualsTest(s1, s2); + OpEqualsTest(s1, s2); } // @@ -232,7 +225,7 @@ public struct Point3D } private static void TestUnmanaged() { - var thing = new Point3D { X = 1, Y = 2, Z = 3 }; + Point3D thing = new() { X = 1, Y = 2, Z = 3 }; var storage = thing.ToByteArray(); @@ -276,7 +269,6 @@ private static void TestEnumValues() foreach (var pair in map) Console.WriteLine($"{pair.Key}:\t{pair.Value}"); - // } } diff --git a/docs/csharp/programming-guide/types/boxing-and-unboxing.md b/docs/csharp/programming-guide/types/boxing-and-unboxing.md index 444cfa262d5c1..daff88a52b229 100644 --- a/docs/csharp/programming-guide/types/boxing-and-unboxing.md +++ b/docs/csharp/programming-guide/types/boxing-and-unboxing.md @@ -1,7 +1,7 @@ --- title: "Boxing and Unboxing" -description: Learn about boxing and unboxing in C# programming. See code examples and view additional available resources. -ms.date: 07/20/2015 +description: Learn about boxing and unboxing in C# programming. See code examples and view other available resources. +ms.date: 10/13/2025 f1_keywords: - "cs.boxing" helpviewer_keywords: @@ -61,7 +61,7 @@ This example converts an integer variable `i` to an object `o` by using boxing. Unboxing is an explicit conversion from the type `object` to a [value type](../../language-reference/builtin-types/value-types.md) or from an interface type to a value type that implements the interface. An unboxing operation consists of: -- Checking the object instance to make sure that it is a boxed value of the given value type. +- Checking the object instance to make sure that it's a boxed value of the given value type. - Copying the value from the instance into the value-type variable. @@ -91,13 +91,13 @@ If you change the statement: int j = (short)o; ``` -to: +To: ```csharp int j = (int)o; ``` -the conversion will be performed, and you will get the output: +The conversion is performed, and you'll get the output: `Unboxing OK.` diff --git a/docs/csharp/programming-guide/types/snippets/index/Program.cs b/docs/csharp/programming-guide/types/snippets/index/Program.cs index 47021bd1e0fa5..648300797b168 100644 --- a/docs/csharp/programming-guide/types/snippets/index/Program.cs +++ b/docs/csharp/programming-guide/types/snippets/index/Program.cs @@ -22,15 +22,10 @@ public string GetName(int ID) } // -public struct Coords +public struct Coords(int x, int y) { - public int x, y; - - public Coords(int p1, int p2) - { - x = p1; - y = p2; - } + public int X { get; init; } = x; + public int Y { get; init; } = y; } // @@ -127,7 +122,7 @@ static void Declarations() static void DeclarationAndAssignment() { // - MyClass myClass = new MyClass(); + MyClass myClass = new(); MyClass myClass2 = myClass; // } @@ -135,7 +130,7 @@ static void DeclarationAndAssignment() static void InterfaceAssignment() { // - MyClass myClass = new MyClass(); + MyClass myClass = new(); // Declare and assign using an existing value. IMyInterface myInterface = myClass; diff --git a/docs/fsharp/language-reference/interpolated-strings.md b/docs/fsharp/language-reference/interpolated-strings.md index e9aeed973853d..4080184b60fa8 100644 --- a/docs/fsharp/language-reference/interpolated-strings.md +++ b/docs/fsharp/language-reference/interpolated-strings.md @@ -2,6 +2,7 @@ title: Interpolated strings description: Learn about interpolated strings, a special form of string that allows you to embed F# expressions directly inside them. ms.date: 10/01/2025 +ai-usage: ai-assisted --- # Interpolated strings @@ -33,6 +34,9 @@ printfn $"I think {3.0 + 0.14} is close to {System.Math.PI}!" The contents in between each `{}` brace pair can be any F# expression. +For non-typed interpolated strings (without format specifiers), the expression is converted to a string using the `ToString()` method. If the expression evaluates to `null`, an empty string is used. + +For typed interpolated strings with format specifiers (such as `%s{expr}` or `%d{expr}`), the conversion follows the rules defined for that specific format specifier. To escape a `{}` brace pair, write two of them like so: ```fsharp diff --git a/docs/fsharp/language-reference/nameof.md b/docs/fsharp/language-reference/nameof.md index 575e58125af60..b23bffdb00351 100644 --- a/docs/fsharp/language-reference/nameof.md +++ b/docs/fsharp/language-reference/nameof.md @@ -2,6 +2,7 @@ title: Nameof description: Learn about the nameof operator, a metaprogramming feature that allows you to produce the name of any symbol in your source code. ms.date: 11/12/2020 +ai-usage: ai-assisted --- # Nameof @@ -77,7 +78,33 @@ The reason why the syntax is different is to align with other F# intrinsic opera ## Nameof in pattern matching -The [`nameof` pattern](pattern-matching.md#nameof-pattern) lets you use `nameof` in a pattern match expression like so: +The [`nameof` pattern](pattern-matching.md#nameof-pattern) lets you use `nameof` in a pattern match expression. This is particularly useful when matching string values against the names of symbols in your code, providing compile-time safety and automatic updates when you refactor. + +A practical example is deserializing events or messages where string values represent type or case names: + +```fsharp +type EventType = + | OrderCreated + | OrderShipped + | OrderDelivered + +let handleEvent eventName data = + match eventName with + | nameof OrderCreated -> printfn "Processing order creation: %s" data + | nameof OrderShipped -> printfn "Processing order shipment: %s" data + | nameof OrderDelivered -> printfn "Processing order delivery: %s" data + | _ -> printfn "Unknown event type: %s" eventName + +handleEvent "OrderCreated" "Order #123" // matches first case +``` + +Using `nameof` instead of string literals like `"OrderCreated"` provides several benefits: + +- If you rename a discriminated union case, the pattern automatically updates. +- The compiler prevents typos by ensuring the symbol exists. +- Your code remains consistent during refactoring. + +You can also use `nameof` with parameters: ```fsharp let f (str: string) = diff --git a/docs/fsharp/language-reference/pattern-matching.md b/docs/fsharp/language-reference/pattern-matching.md index 83ccd0bff162d..eca14fe0d96ac 100644 --- a/docs/fsharp/language-reference/pattern-matching.md +++ b/docs/fsharp/language-reference/pattern-matching.md @@ -2,6 +2,7 @@ title: Pattern Matching description: Learn how patterns are used in F# to compare data with logical structures, decompose data into constituent parts, or extract information from data. ms.date: 11/12/2020 +ai-usage: ai-assisted --- # Pattern Matching @@ -184,15 +185,30 @@ The following code shows some additional uses of the wildcard pattern: ## Patterns That Have Type Annotations -Patterns can have type annotations. These behave like other type annotations and guide inference like other type annotations. Parentheses are required around type annotations in patterns. The following code shows a pattern that has a type annotation. +Patterns can have type annotations. These behave like other type annotations and guide inference like other type annotations. Parentheses are required around type annotations in patterns. + +A pattern with a type annotation uses the syntax `pattern : type` and provides **compile-time type information** to the type checker. This is purely a static type annotation that helps with type inference - it doesn't perform any runtime type checking or conversion. The compiler uses this information during compilation to determine the type of the pattern variable. + +The following code shows a pattern that has a type annotation: [!code-fsharp[Main](~/samples/snippets/fsharp/lang-ref-2/snippet4815.fs)] +In this example, `(var1 : int)` tells the compiler that `var1` is of type `int`. This is resolved at compile time, and the generated code treats `var1` as an integer throughout the match expression. This pattern will match any integer value and bind it to `var1`. + +**Key characteristics:** + +- Uses the syntax `pattern : type` (with a single colon). +- Resolved at **compile time** - provides type information to the type checker. +- Does not perform runtime type testing. +- Used for type inference and to guide the compiler. + ## Type Test Pattern -The type test pattern is used to match the input against a type. If the input type is a match to (or a derived type of) the type specified in the pattern, the match succeeds. +The type test pattern is used to match the input against a type **at runtime**. If the input type is a match to (or a derived type of) the type specified in the pattern, the match succeeds. + +A type test pattern uses the syntax `:? type` and performs **runtime type checking**, similar to the `is` or `as` operators in C#. This pattern tests whether a value is of a specific type during program execution, making it useful when working with inheritance hierarchies or interface implementations. -The following example demonstrates the type test pattern. +The following example demonstrates the type test pattern: [!code-fsharp[Main](~/samples/snippets/fsharp/lang-ref-2/snippet4816.fs)] @@ -210,6 +226,49 @@ let m (a: A) = | _ -> () ``` +**Key characteristics:** + +- Uses the syntax `:? type` or `:? type as identifier` (with a question mark). +- Resolved at **runtime** - performs actual type checking during execution. +- Tests if a value is an instance of a specific type or its derived types. +- Commonly used with inheritance hierarchies and polymorphic types. +- Similar to C#'s `is` operator or `as` operator. + +### Contrasting Type Annotations and Type Test Patterns + +While both patterns involve types, they serve very different purposes: + +| Feature | Type Annotation Pattern (`pattern : type`) | Type Test Pattern (`:? type`) | +|---------|-------------------------------------------|-------------------------------| +| **Syntax** | Single colon: `a : int` | Colon with question mark: `:? Button` | +| **When resolved** | Compile time | Runtime | +| **Purpose** | Guides type inference | Tests actual type of value | +| **Use case** | Helping the compiler understand types | Checking runtime types in inheritance hierarchies | +| **Equivalent in C#** | Type annotations in switch patterns | `is` or `as` operators | + +The following example demonstrates the differences: + +```fsharp +// Type annotation pattern - compile time +let detect1 x = + match x with + | 1 -> printfn "Found a 1!" + | (var1 : int) -> printfn "%d" var1 +// The ': int' tells the compiler var1 is an int +// Everything is resolved at compile time + +// Type test pattern - runtime +type A() = class end +type B() = inherit A() + +let test (a: A) = + match a with + | :? B -> printfn "Runtime check: it's a B" + | _ -> printfn "Runtime check: it's not a B" +// The ':? B' performs a runtime type check +// The actual type is tested during execution +``` + ## Null Pattern The null pattern matches the null value that can appear when you are working with types that allow a null value. Null patterns are frequently used when interoperating with .NET Framework code. For example, the return value of a .NET API might be the input to a `match` expression. You can control program flow based on whether the return value is null, and also on other characteristics of the returned value. You can use the null pattern to prevent null values from propagating to the rest of your program. @@ -238,7 +297,34 @@ let let str = // str is inferred to be `string | null` ## Nameof pattern -The `nameof` pattern matches against a string when its value is equal to the expression that follows the `nameof` keyword. for example: +The `nameof` pattern matches against a string when its value is equal to the expression that follows the `nameof` keyword. This pattern is particularly useful when you need to match string values against the names of types, discriminated union cases, or other symbols in your code. Using `nameof` provides compile-time safety because if you rename a symbol, the pattern will automatically use the new name. + +A common use case is deserializing data where string values represent type or case names: + +```fsharp +type EventType = + | OrderCreated + | OrderShipped + | OrderDelivered + +let handleEvent eventName data = + match eventName with + | nameof OrderCreated -> printfn "Processing order creation: %s" data + | nameof OrderShipped -> printfn "Processing order shipment: %s" data + | nameof OrderDelivered -> printfn "Processing order delivery: %s" data + | _ -> printfn "Unknown event type: %s" eventName + +handleEvent "OrderCreated" "Order #123" // matches first case +handleEvent "OrderShipped" "Order #123" // matches second case +``` + +This approach is better than using string literals (like `"OrderCreated"`) because: + +- If you rename `OrderCreated` to `OrderPlaced`, the pattern automatically updates. +- The compiler ensures that the symbol exists, preventing typos. +- Your code remains consistent when refactoring. + +You can also use `nameof` with parameters: ```fsharp let f (str: string) = diff --git a/docs/fsharp/tools/fsharp-interactive/index.md b/docs/fsharp/tools/fsharp-interactive/index.md index e34b7988efae3..fc75275801f3e 100644 --- a/docs/fsharp/tools/fsharp-interactive/index.md +++ b/docs/fsharp/tools/fsharp-interactive/index.md @@ -1,9 +1,10 @@ --- title: F# Interactive (dotnet) Reference description: Learn how F# Interactive (dotnet fsi) is used to run F# code interactively at the console or to execute F# scripts. -ms.date: 11/29/2020 +ms.date: 10/13/2025 f1_keywords: - VS.ToolsOptionsPages.F#_Tools.F#_Interactive +ai-usage: ai-assisted --- # Interactive programming with F\# @@ -170,17 +171,29 @@ Examples: ### Specifying a package source -You can also specify a package source with the `#i` command. The following example specifies a remote and a local source: +You can also specify a package source with the `#i` command. The following examples specify remote and local sources: ```fsharp #i "nuget: https://my-remote-package-source/index.json" #i """nuget: C:\path\to\my\local\source""" +#i "nuget: /Users/username/path/to/my/local/source" +#i "nuget: /home/username/path/to/my/local/source" ``` -This will tell the resolution engine under the covers to also take into account the remote and/or local sources added to a script. +This tells the resolution engine to take into account the remote and/or local sources added to a script. -You can specify as many package references as you like in a script. +You can specify as many package sources as you like in a script. +> [!IMPORTANT] +> Relative paths aren't currently supported with the `#i` directive. You must use absolute paths for local package sources. This limitation is tracked in [dotnet/fsharp#12969](https://github.com/dotnet/fsharp/issues/12969). +> +> **Workaround:** You can programmatically construct an absolute path using `__SOURCE_DIRECTORY__` and `System.IO.Path.Combine()`, then use string interpolation to pass it to the `#i` directive. For example: +> +> ```fsharp +> let localSource = System.IO.Path.Combine(__SOURCE_DIRECTORY__, "relative/path/to/my/local/source") +> #i $"""nuget: {localSource}""" +> ``` +> > [!NOTE] > There's currently a limitation for scripts that use framework references (e.g.`Microsoft.NET.Sdk.Web` or `Microsoft.NET.Sdk.WindowsDesktop`). Packages like Saturn, Giraffe, WinForms are not available. This is being tracked in issue [#9417](https://github.com/dotnet/fsharp/issues/9417). > WinForms, still works in the .NET Framework version of F# Interactive. diff --git a/docs/fundamentals/code-analysis/quality-rules/ca2224.md b/docs/fundamentals/code-analysis/quality-rules/ca2224.md index 9cd7d59ce412b..41510d644f12e 100644 --- a/docs/fundamentals/code-analysis/quality-rules/ca2224.md +++ b/docs/fundamentals/code-analysis/quality-rules/ca2224.md @@ -29,7 +29,7 @@ A public type implements the equality operator but doesn't override method. If you implement the equality operator, its logic must be identical to that of . > [!NOTE] -> This rule only applies to Visual Basic code. The C# compiler generates a separate warning, [CS0660](../../../csharp/misc/cs0660.md). +> This rule only applies to Visual Basic code. The C# compiler generates a separate warning, [CS0660](../../../csharp/language-reference/compiler-messages/overloaded-operator-errors.md#equality-operators). ## How to fix violations @@ -78,4 +78,4 @@ The following example fixes the violation by overriding mixedList = new List(); + List mixedList = []; // Add a string element to the list. mixedList.Add("First Group:"); @@ -79,8 +79,7 @@ static void Main() sum += (int)mixedList[j] * (int)mixedList[j]; } - // The sum displayed is 30, the sum of 1 + 4 + 9 + 16. - Console.WriteLine("Sum: " + sum); + Console.WriteLine($"Sum: {sum}"); // Output: // Answer42True @@ -186,11 +185,11 @@ static void Main() { int j = (short)o; // attempt to unbox - System.Console.WriteLine("Unboxing OK."); + Console.WriteLine("Unboxing OK."); } - catch (System.InvalidCastException e) + catch (InvalidCastException e) { - System.Console.WriteLine($"{e.Message} Error: Incorrect unboxing."); + Console.WriteLine($"{e.Message} Error: Incorrect unboxing."); } } } @@ -223,7 +222,7 @@ static void Main(string[] args) { // byte[] bytes = BitConverter.GetBytes(201805978); - Console.WriteLine("byte array: " + BitConverter.ToString(bytes)); + Console.WriteLine($"byte array: {BitConverter.ToString(bytes)}"); // Output: byte array: 9A-50-07-0C // } @@ -268,7 +267,7 @@ Hexadecimal value of ! is 21 // Convert the number expressed in base-16 to an integer. int value = Convert.ToInt32(hex, 16); // Get the character corresponding to the integral value. - string stringValue = Char.ConvertFromUtf32(value); + string stringValue = char.ConvertFromUtf32(value); char charValue = (char)value; Console.WriteLine("hexadecimal value = {0}, int value = {1}, char value = {2} or {3}", hex, value, stringValue, charValue); @@ -291,7 +290,7 @@ Hexadecimal value of ! is 21 // string hexString = "8E2"; - int num = Int32.Parse(hexString, System.Globalization.NumberStyles.HexNumber); + int num = int.Parse(hexString, System.Globalization.NumberStyles.HexNumber); Console.WriteLine(num); //Output: 2274 // diff --git a/samples/snippets/csharp/objectoriented/accessmodifiers.cs b/samples/snippets/csharp/objectoriented/accessmodifiers.cs index 09f65959165d6..76d8cd76478cc 100644 --- a/samples/snippets/csharp/objectoriented/accessmodifiers.cs +++ b/samples/snippets/csharp/objectoriented/accessmodifiers.cs @@ -18,10 +18,7 @@ protected void Pedal() { } private int _wheels = 3; // protected internal property: - protected internal int Wheels - { - get { return _wheels; } - } + protected internal int Wheels => _wheels; } // -} +} \ No newline at end of file