From 0490d52b04299e02046323eed6b94e149aa04a39 Mon Sep 17 00:00:00 2001 From: Bill Wagner Date: Fri, 17 Jan 2025 14:57:18 -0500 Subject: [PATCH] Update coding conventions for C# 13 (#44283) * Fix small open issues. - #33341 already addressed in #36428. - Fixes #37294: Add text that the shortened format is valid only when the runtime type matches the variable type. - Fixes #37295: Don't use `ID` in the sample. - Fixes #37296: Fix nullable warnings. Other issue comments are incorrect. - Fixes #41748: Change the sample so the constructor is relevant. - Fixes #42858: Add an explanation on declaring variables in top level statements. * Remaining open issues - Fixes #43838: Add notes for collection expressions and using primary constructors. Include naming recommendations. - Fixes #43839 Update the overview of constructors to include information on primary constructors. * Code review for style Fix any style issues with the current code. * Final edit pass I also caught a couple sample bits that I'd neglected in the previous commit. * Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --------- Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../coding-style/coding-conventions.md | 69 ++++++---- .../coding-conventions.csproj | 3 +- .../snippets/coding-conventions/program.cs | 122 ++++++++++-------- .../classes-and-structs/constructors.md | 35 +++-- .../snippets/constructors/Program.cs | 17 ++- .../snippets/constructors/constructors.csproj | 2 +- .../snippets/using-constructors/Program.cs | 23 ++-- .../UsingConstructors.csproj | 2 +- .../classes-and-structs/using-constructors.md | 26 ++-- docs/csharp/tutorials/top-level-statements.md | 8 +- 10 files changed, 169 insertions(+), 138 deletions(-) diff --git a/docs/csharp/fundamentals/coding-style/coding-conventions.md b/docs/csharp/fundamentals/coding-style/coding-conventions.md index 168dec7ba6a75..87fcd8265536d 100644 --- a/docs/csharp/fundamentals/coding-style/coding-conventions.md +++ b/docs/csharp/fundamentals/coding-style/coding-conventions.md @@ -1,7 +1,7 @@ --- title: ".NET Coding Conventions" description: Learn about commonly used coding conventions in C#. Coding conventions create a consistent look to the code and facilitate copying, changing, and maintaining the code. This article also includes the docs repo coding guidelines -ms.date: 07/27/2023 +ms.date: 01/15/2025 helpviewer_keyword: - "coding conventions, C#" - "Visual C#, coding conventions" @@ -19,19 +19,19 @@ We chose our conventions based on the following goals: 1. *Adoption*: We aggressively update our samples to use new language features. That practice raises awareness of new features, and makes them more familiar to all C# developers. > [!IMPORTANT] -> These guidelines are used by Microsoft to develop samples and documentation. They were adopted from the [.NET Runtime, C# Coding Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) and [C# compiler (roslyn)](https://github.com/dotnet/roslyn/blob/main/CONTRIBUTING.md#csharp) guidelines. We chose those guidelines because they have been tested over several years of Open Source development. They've helped community members participate in the runtime and compiler projects. They are meant to be an example of common C# conventions, and not an authoritative list (see [Framework Design Guidelines](../../../standard/design-guidelines/index.md) for that). +> These guidelines are used by Microsoft to develop samples and documentation. They were adopted from the [.NET Runtime, C# Coding Style](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md) and [C# compiler (roslyn)](https://github.com/dotnet/roslyn/blob/main/CONTRIBUTING.md#csharp) guidelines. We chose those guidelines because of their adoption over several years of Open Source development. These guidelines help community members participate in the runtime and compiler projects. They're meant to be an example of common C# conventions, and not an authoritative list (see [Framework Design Guidelines](../../../standard/design-guidelines/index.md) for detailed guidelines). > > The *teaching* and *adoption* goals are why the docs coding convention differs from the runtime and compiler conventions. Both the runtime and compiler have strict performance metrics for hot paths. Many other applications don't. Our *teaching* goal mandates that we don't prohibit any construct. Instead, samples show when constructs should be used. We update samples more aggressively than most production applications do. Our *adoption* goal mandates that we show code you should write today, even when code written last year doesn't need changes. -This article explains our guidelines. The guidelines have evolved over time, and you'll find samples that don't follow our guidelines. We welcome PRs that bring those samples into compliance, or issues that draw our attention to samples we should update. Our guidelines are Open Source and we welcome PRs and issues. However, if your submission would change these recommendations, open an issue for discussion first. You're welcome to use our guidelines, or adapt them to your needs. +This article explains our guidelines. The guidelines evolve over time, and you'll find samples that don't follow our guidelines. We welcome PRs that bring those samples into compliance, or issues that draw our attention to samples we should update. Our guidelines are Open Source and we welcome PRs and issues. However, if your submission would change these recommendations, open an issue for discussion first. You're welcome to use our guidelines, or adapt them to your needs. ## Tools and analyzers -Tools can help your team enforce your conventions. You can enable [code analysis](../../../fundamentals/code-analysis/overview.md) to enforce the rules you prefer. You can also create an [editorconfig](/visualstudio/ide/create-portable-custom-editor-options) so that Visual Studio automatically enforces your style guidelines. As a starting point, you can copy the [dotnet/docs repo's file](https://github.com/dotnet/docs/blob/main/.editorconfig) to use our style. +Tools can help your team enforce your conventions. You can enable [code analysis](../../../fundamentals/code-analysis/overview.md) to enforce the rules you prefer. You can also create an [editorconfig](/visualstudio/ide/create-portable-custom-editor-options) so that Visual Studio automatically enforces your style guidelines. As a starting point, you can copy the [`dotnet/docs` *.editorconfig*](https://github.com/dotnet/docs/blob/main/.editorconfig) to use our style. -These tools make it easier for your team to adopt your preferred guidelines. Visual Studio applies the rules in all `.editorconfig` files in scope to format your code. You can use multiple configurations to enforce corporate-wide conventions, team conventions, and even granular project conventions. +These tools make it easier for your team to adopt your preferred guidelines. Visual Studio applies the rules in all *.editorconfig* files in scope to format your code. You can use multiple configurations to enforce corporate-wide conventions, team conventions, and even granular project conventions. -Code analysis produces warnings and diagnostics when the enabled rules are violated. You configure the rules you want applied to your project. Then, each CI build notifies developers when they violate any of the rules. +Code analysis produces warnings and diagnostics when it detects rule violations. You configure the rules you want applied to your project. Then, each CI build notifies developers when they violate any of the rules. ### Diagnostic IDs @@ -42,13 +42,13 @@ Code analysis produces warnings and diagnostics when the enabled rules are viola The following sections describe practices that the .NET docs team follows to prepare code examples and samples. In general, follow these practices: - Utilize modern language features and C# versions whenever possible. -- Avoid obsolete or outdated language constructs. -- Only catch exceptions that can be properly handled; avoid catching generic exceptions. +- Avoid outdated language constructs. +- Only catch exceptions that can be properly handled; avoid catching general exceptions. For example, sample code shouldn't catch the type without an exception filter. - Use specific exception types to provide meaningful error messages. - Use LINQ queries and methods for collection manipulation to improve code readability. - Use asynchronous programming with async and await for I/O-bound operations. - Be cautious of deadlocks and use when appropriate. -- Use the language keywords for data types instead of the runtime types. For example, use `string` instead of , or `int` instead of . +- Use the language keywords for data types instead of the runtime types. For example, use `string` instead of , or `int` instead of . This recommendation includes using the types `nint` and `nuint`. - Use `int` rather than unsigned types. The use of `int` is common throughout C#, and it's easier to interact with other libraries when you use `int`. Exceptions are for documentation specific to unsigned data types. - Use `var` only when a reader can infer the type from the expression. Readers view our samples on the docs platform. They don't have hover or tool tips that display the type of variables. - Write code with clarity and simplicity in mind. @@ -66,15 +66,30 @@ More specific guidelines follow. :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet7"::: -### Arrays +- Prefer raw string literals to escape sequences or verbatim strings. -- Use the concise syntax when you initialize arrays on the declaration line. In the following example, you can't use `var` instead of `string[]`. + :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="rawStringLiterals"::: -:::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet13a"::: +- Use the expression-based string interpolation rather than positional string interpolation. -- If you use explicit instantiation, you can use `var`. + :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="interpolatedStrings"::: -:::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet13b"::: +### Constructors and initialization + +- Use Pascal case for primary constructor parameters on record types: + + :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="PrimaryRecord"::: + +- Use camel case for primary constructor parameters on class and struct types. +- Use `required` properties instead of constructors to force initialization of property values: + + :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="PrimaryClass"::: + +### Arrays and collections + +- Use collection expressions to initialize all collection types: + +:::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet13"::: ### Delegates @@ -128,7 +143,7 @@ If the divisor is 0, the second clause in the `if` statement would cause a run-t ### `new` operator -- Use one of the concise forms of object instantiation, as shown in the following declarations. +- Use one of the concise forms of object instantiation when the variable type matches the object type, as shown in the following declarations. This form isn't valid when the variable is an interface type, or a base class of the runtime type. :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet19"::: @@ -160,7 +175,7 @@ The lambda expression shortens the following traditional definition. ### Static members -Call [static](../../language-reference/keywords/static.md) members by using the class name: *ClassName.StaticMember*. This practice makes code more readable by making static access clear. Don't qualify a static member defined in a base class with the name of a derived class. While that code compiles, the code readability is misleading, and the code may break in the future if you add a static member with the same name to the derived class. +Call [static](../../language-reference/keywords/static.md) members by using the class name: *ClassName.StaticMember*. This practice makes code more readable by making static access clear. Don't qualify a static member defined in a base class with the name of a derived class. While that code compiles, the code readability is misleading, and the code might break in the future if you add a static member with the same name to the derived class. ### LINQ queries @@ -172,7 +187,7 @@ Call [static](../../language-reference/keywords/static.md) members by using the :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet26"::: -- Rename properties when the property names in the result would be ambiguous. For example, if your query returns a customer name and a distributor ID, instead of leaving them as `Name` and `ID` in the result, rename them to clarify that `Name` is the name of a customer, and `ID` is the ID of a distributor. +- Rename properties when the property names in the result would be ambiguous. For example, if your query returns a customer name and a distributor name, instead of leaving them as a form of `Name` in the result, rename them to clarify `CustomerName` is the name of a customer, and `DistributorName` is the name of a distributor. :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet27"::: @@ -186,7 +201,7 @@ Call [static](../../language-reference/keywords/static.md) members by using the :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet29"::: -- Use multiple `from` clauses instead of a [`join`](../../language-reference/keywords/join-clause.md) clause to access inner collections. For example, a collection of `Student` objects might each contain a collection of test scores. When the following query is executed, it returns each score that is over 90, along with the last name of the student who received the score. +- Access inner collections with multiple `from` clauses instead of a [`join`](../../language-reference/keywords/join-clause.md) clause. For example, a collection of `Student` objects might each contain a collection of test scores. When the following query is executed, it returns each score that is over 90, along with the family name of the student who received the score. :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet30"::: @@ -196,7 +211,7 @@ Call [static](../../language-reference/keywords/static.md) members by using the :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet8"::: -- Don't use [var](../../language-reference/statements/declarations.md#implicitly-typed-local-variables) when the type isn't apparent from the right side of the assignment. Don't assume the type is clear from a method name. A variable type is considered clear if it's a `new` operator, an explicit cast or assignment to a literal value. +- Don't use [var](../../language-reference/statements/declarations.md#implicitly-typed-local-variables) when the type isn't apparent from the right side of the assignment. Don't assume the type is clear from a method name. A variable type is considered clear if it's a `new` operator, an explicit cast, or assignment to a literal value. :::code language="csharp" source="./snippets/coding-conventions/program.cs" id="Snippet9"::: @@ -221,10 +236,18 @@ Call [static](../../language-reference/keywords/static.md) members by using the - use implicit type for the result sequences in LINQ queries. The section on [LINQ](#linq-queries) explains that many LINQ queries result in anonymous types where implicit types must be used. Other queries result in nested generic types where `var` is more readable. > [!NOTE] - > Be careful not to accidentally change a type of an element of the iterable collection. For example, it is easy to switch from to in a `foreach` statement, which changes the execution of a query. + > Be careful not to accidentally change a type of an element of the iterable collection. For example, it's easy to switch from to in a `foreach` statement, which changes the execution of a query. Some of our samples explain the *natural type* of an expression. Those samples must use `var` so that the compiler picks the natural type. Even though those examples are less obvious, the use of `var` is required for the sample. The text should explain the behavior. +### File scoped namespace declarations + +Most code files declare a single namespace. Therefore, our examples should use the file scoped namespace declarations: + +```csharp +namespace MySampleCode; +``` + ### Place the using directives outside the namespace declaration When a `using` directive is outside a namespace declaration, that imported namespace is its fully qualified name. The fully qualified name is clearer. When the `using` directive is inside the namespace, it could be either relative to that namespace, or its fully qualified name. @@ -272,7 +295,7 @@ And it compiles today. And tomorrow. But then sometime next week the preceding ( - error CS0103: The name 'WaitUntil' does not exist in the current context ``` -One of the dependencies has introduced this class in a namespace then ends with `.Azure`: +One of the dependencies introduced this class in a namespace then ends with `.Azure`: ```csharp namespace CoolStuff.Azure @@ -315,14 +338,14 @@ In general, use the following format for code samples: - Use four spaces for indentation. Don't use tabs. - Align code consistently to improve readability. - Limit lines to 65 characters to enhance code readability on docs, especially on mobile screens. -- Break long statements into multiple lines to improve clarity. +- Improve clarity and user experience by breaking long statements into multiple lines. - Use the "Allman" style for braces: open and closing brace its own new line. Braces line up with current indentation level. - Line breaks should occur before binary operators, if necessary. ### Comment style - Use single-line comments (`//`) for brief explanations. -- Avoid multi-line comments (`/* */`) for longer explanations.
Comments in the code samples aren't localized. That means explanations embedded in the code will not be translated. Longer, explanatory text should be placed in the companion article, so that it can be localized. +- Avoid multi-line comments (`/* */`) for longer explanations.
Comments in the code samples aren't localized. That means explanations embedded in the code aren't translated. Longer, explanatory text should be placed in the companion article, so that it can be localized. - For describing methods, classes, fields, and all public members use [XML comments](../../language-reference/xmldoc/index.md). - Place the comment on a separate line, not at the end of a line of code. - Begin comment text with an uppercase letter. diff --git a/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/coding-conventions.csproj b/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/coding-conventions.csproj index ea0eaf187a84a..10875a126b52f 100644 --- a/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/coding-conventions.csproj +++ b/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/coding-conventions.csproj @@ -2,7 +2,8 @@ WinExe - net8.0-windows + enable + net9.0-windows enable enable Coding_Conventions_Examples.Program diff --git a/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/program.cs b/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/program.cs index a514e32fd7a67..10611e00813fb 100644 --- a/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/program.cs +++ b/docs/csharp/fundamentals/coding-style/snippets/coding-conventions/program.cs @@ -61,16 +61,18 @@ static void Main(string[] args) // Save snippet 4 and 5 for possible additions in program structure. - Name[] nameList = {new Name { FirstName = "Anderson", LastName = "Redmond" }, - new Name { FirstName = "Jones", LastName = "Seattle" }, - new Name { FirstName = "Anderson", LastName = "Redmond" }}; + Name[] nameList = [ + new Name { FirstName = "Anderson", LastName = "Redmond" }, + new Name { FirstName = "Jones", LastName = "Seattle" }, + new Name { FirstName = "Anderson", LastName = "Redmond" } + ]; int n = 0; // string displayName = $"{nameList[n].LastName}, {nameList[n].FirstName}"; // - Console.WriteLine("{0}, {1}", nameList[n].LastName, nameList[n].FirstName); + Console.WriteLine($"{nameList[n].LastName}, {nameList[n].FirstName}"); Console.WriteLine(nameList[n].LastName + ", " + nameList[n].FirstName); // @@ -123,12 +125,9 @@ static void Main(string[] args) Console.WriteLine(); // - // - string[] vowels1 = { "a", "e", "i", "o", "u" }; - // - // - var vowels2 = new string[] { "a", "e", "i", "o", "u" }; - // + // + string[] vowels = [ "a", "e", "i", "o", "u" ]; + // // Del exampleDel2 = DelMethod; @@ -152,10 +151,7 @@ static void Main(string[] args) } finally { - if (bodyStyle != null) - { - ((IDisposable)bodyStyle).Dispose(); - } + bodyStyle?.Dispose(); } // // @@ -214,20 +210,14 @@ static void Main(string[] args) ExampleClass.totalInstances = 1; - var customers = new List - { - new Customer { Name = "Jones", ID = 432, City = "Redmond" } - }; + List Customers = [ new Customer { Name = "Jones", ID = 432, City = "Redmond" } ]; // Check shop name to use this. - var distributors = new List - { - new Distributor { Name = "ShopSmart", ID = 11302, City = "Redmond" } - }; + List Distributors = [ new Distributor { Name = "ShopSmart", ID = 11302, City = "Redmond" } ]; // // - var seattleCustomers = from customer in customers + var seattleCustomers = from customer in Customers // where customer.City == "Seattle" select customer.Name; @@ -235,20 +225,20 @@ static void Main(string[] args) // var localDistributors = - from customer in customers - join distributor in distributors on customer.City equals distributor.City + from customer in Customers + join distributor in Distributors on customer.City equals distributor.City select new { Customer = customer, Distributor = distributor }; // // var localDistributors2 = - from customer in customers - join distributor in distributors on customer.City equals distributor.City - select new { CustomerName = customer.Name, DistributorID = distributor.ID }; + from customer in Customers + join distributor in Distributors on customer.City equals distributor.City + select new { CustomerName = customer.Name, DistributorName = distributor.Name }; // // - var seattleCustomers2 = from customer in customers + var seattleCustomers2 = from customer in Customers where customer.City == "Seattle" orderby customer.Name select customer; @@ -257,13 +247,13 @@ orderby customer.Name // #30 is in class CompoundFrom var customerDistributorNames = - from customer in customers - join distributor in distributors on customer.City equals distributor.City + from customer in Customers + join distributor in Distributors on customer.City equals distributor.City select new { CustomerName = customer.Name, DistributorID = distributor.ID }; var customerDistributorNames2 = - from customer in customers - from distributor in distributors + from customer in Customers + from distributor in Distributors where customer.City == distributor.City select new { CustomerName = customer.Name, DistributorID = distributor.ID }; @@ -335,20 +325,14 @@ public static int IncrementTotal() return totalInstances; } - public static int ResultSoFar() - { - return 0; - } + public static int ResultSoFar() => 0; } class BaseClass { protected static int totalInstances; - static BaseClass() - { - totalInstances = 0; - } + static BaseClass() => totalInstances = 0; public static int IncrementTotal() { @@ -367,7 +351,7 @@ class CompoundFrom public class Student { public string? LastName { get; set; } - public List? Scores { get; set; } + public ICollection Scores { get; set; } = default!; } static void Main() @@ -375,32 +359,37 @@ static void Main() // Use a collection initializer to create the data source. Note that // each element in the list contains an inner sequence of scores. - List students = new List - { - new Student {LastName="Omelchenko", Scores= new List {97, 72, 81, 60}}, - new Student {LastName="O'Donnell", Scores= new List {75, 84, 91, 39}}, - new Student {LastName="Mortensen", Scores= new List {88, 94, 65, 85}}, - new Student {LastName="Garcia", Scores= new List {97, 89, 85, 82}}, - new Student {LastName="Beebe", Scores= new List {35, 72, 91, 70}} - }; + List students = [ + new Student {LastName="Omelchenko", Scores = [97, 72, 81, 60]}, + new Student {LastName="O'Donnell", Scores = [75, 84, 91, 39]}, + new Student {LastName="Mortensen", Scores = [88, 94, 65, 85]}, + new Student {LastName="Garcia", Scores = [97, 89, 85, 82]}, + new Student {LastName="Beebe", Scores = [35, 72, 91, 70]} + ]; // var scoreQuery = from student in students - from score in student.Scores! + from score in student.Scores where score > 90 select new { Last = student.LastName, score }; // + // // Execute the queries. Console.WriteLine("scoreQuery:"); foreach (var student in scoreQuery) { - Console.WriteLine("{0} Score: {1}", student.Last, student.score); + Console.WriteLine($"{student.Last} Score: {student.score}"); } - - // Keep the console window open in debug mode. - Console.WriteLine("Press any key to exit."); - Console.ReadKey(); + // + + // + var message = """ + This is a long message that spans across multiple lines. + It uses raw string literals. This means we can + also include characters like \n and \t without escaping them. + """; + // } } } @@ -459,7 +448,7 @@ public class List { /*...*/ } // // - public int IComparer() { return 0; } + public int IComparer() => 0; public delegate bool Predicate(T item); public struct Nullable where T : struct { /*...*/ } // @@ -475,3 +464,22 @@ public interface ISessionChannel } }//WrapParameters } + +namespace Constructors +{ + // + public record Person(string FirstName, string LastName); + // + + // + public class LabelledContainer(string label) + { + public string Label { get; } = label; + public required T Contents + { + get; + init; + } + } + // +} diff --git a/docs/csharp/programming-guide/classes-and-structs/constructors.md b/docs/csharp/programming-guide/classes-and-structs/constructors.md index fa089538c295f..88c8232459985 100644 --- a/docs/csharp/programming-guide/classes-and-structs/constructors.md +++ b/docs/csharp/programming-guide/classes-and-structs/constructors.md @@ -1,7 +1,7 @@ --- title: "Constructors" description: A constructor in C# is called when a class or struct is created. Use constructors to set defaults, limit instantiation, and write flexible, easy-to-read code. -ms.date: 04/06/2023 +ms.date: 01/15/2025 helpviewer_keywords: - "constructors [C#]" - "classes [C#], constructors" @@ -9,34 +9,38 @@ helpviewer_keywords: --- # Constructors (C# programming guide) -Whenever an instance of a [class](../../language-reference/keywords/class.md) or a [struct](../../language-reference/builtin-types/struct.md) is created, its constructor is called. A class or struct may have multiple constructors that take different arguments. Constructors enable the programmer to set default values, limit instantiation, and write code that is flexible and easy to read. For more information and examples, see [Instance constructors](instance-constructors.md) and [Using constructors](using-constructors.md). +A *constructor* is a method called by the runtime when an instance of a [class](../../language-reference/keywords/class.md) or a [struct](../../language-reference/builtin-types/struct.md) is created. A class or struct can have multiple constructors that take different arguments. Constructors enable you to ensure that instances of the type are valid when created. For more information and examples, see [Instance constructors](instance-constructors.md) and [Using constructors](using-constructors.md). -There are several actions that are part of initializing a new instance. Those actions take place in the following order: +There are several actions that are part of initializing a new instance. The following actions take place in the following order: -1. *Instance fields are set to 0*. This is typically done by the runtime. +1. *Instance fields are set to 0*. This initialization is typically done by the runtime. 1. *Field initializers run*. The field initializers in the most derived type run. 1. *Base type field initializers run*. Field initializers starting with the direct base through each base type to . 1. *Base instance constructors run*. Any instance constructors, starting with through each base class to the direct base class. 1. *The instance constructor runs*. The instance constructor for the type runs. -1. *Object initializers run*. If the expression includes any object initializers, those run after the instance constructor runs. Object initializers run in the textual order. +1. *Object initializers run*. If the expression includes any object initializers, they run after the instance constructor runs. Object initializers run in the textual order. -The preceding actions take place when a new instance is initialized. If a new instance of a `struct` is set to its `default` value, all instance fields are set to 0. +The preceding actions take place when an instance is created using the [`new` operator](../../language-reference//operators/new-operator.md). If a new instance of a `struct` is set to its `default` value, all instance fields are set to 0. Elements of an array are set to their default value of 0 or `null` when an array is created. -If the [static constructor](static-constructors.md) hasn't run, the static constructor runs before any of the instance constructor actions take place. +The [static constructor](static-constructors.md), if any, runs before any of the instance constructor actions take place for any instance of the type. The static constructor runs at most once. ## Constructor syntax -A constructor is a method whose name is the same as the name of its type. Its method signature includes only an optional [access modifier](./access-modifiers.md), the method name and its parameter list; it does not include a return type. The following example shows the constructor for a class named `Person`. +A constructor is a method with the same name as its type. Its method signature can include an optional [access modifier](./access-modifiers.md), the method name, and its parameter list; it doesn't include a return type. The following example shows the constructor for a class named `Person`. :::code source="./snippets/constructors/Program.cs" id="InstanceCtor"::: -If a constructor can be implemented as a single statement, you can use an [expression body definition](../statements-expressions-operators/expression-bodied-members.md). The following example defines a `Location` class whose constructor has a single string parameter named *name*. The expression body definition assigns the argument to the `locationName` field. +If a constructor can be implemented as a single statement, you can use an [expression body member](../statements-expressions-operators/expression-bodied-members.md). The following example defines a `Location` class whose constructor has a single string parameter named *name*. The expression body definition assigns the argument to the `locationName` field. :::code source="./snippets/constructors/Program.cs" id="ExpressionBodiedCtor"::: +If a type requires a parameter to create an instance, you can use a *primary constructor* to indicate that one or more parameters are required to instantiate the type, as shown in the following example: + +:::code source="./snippets/constructors/Program.cs" id="PrimaryCtor"::: + ## Static constructors -The previous examples have all shown instance constructors, which create a new object. A class or struct can also have a static constructor, which initializes static members of the type. Static constructors are parameterless. If you don't provide a static constructor to initialize static fields, the C# compiler initializes static fields to their default value as listed in the [Default values of C# types](../../language-reference/builtin-types/default-values.md) article. +The previous examples show instance constructors, which initialize a new object. A class or struct can also declare a static constructor, which initializes static members of the type. Static constructors are parameterless. If you don't provide a static constructor to initialize static fields, the C# compiler initializes static fields to their default value as listed in the [Default values of C# types](../../language-reference/builtin-types/default-values.md) article. The following example uses a static constructor to initialize a static field. @@ -48,17 +52,8 @@ You can also define a static constructor with an expression body definition, as For more information and examples, see [Static Constructors](./static-constructors.md). -## In This Section - -- [Using constructors](./using-constructors.md) -- [Instance constructors](./instance-constructors.md) -- [Private constructors](./private-constructors.md) -- [Static constructors](./static-constructors.md) -- [How to write a copy constructor](./how-to-write-a-copy-constructor.md) - ## See also - [The C# type system](../../fundamentals/types/index.md) -- [Finalizers](./finalizers.md) -- [static](../../language-reference/keywords/static.md) +- [`static`](../../language-reference/keywords/static.md) - [Why Do Initializers Run In The Opposite Order As Constructors? Part One](/archive/blogs/ericlippert/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one) diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/Program.cs index 74adb19a8cf21..9cd7ee626742a 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/Program.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/Program.cs @@ -25,10 +25,7 @@ public class Adult : Person public Adult(string lastName, string firstName) : base(lastName, firstName) { } - static Adult() - { - minimumAge = 18; - } + static Adult() => minimumAge = 18; // Remaining implementation of Adult class. } @@ -63,3 +60,15 @@ public string Name } // +// +public class LabelledContainer(string label) +{ + public string Label { get; } = label; + public required T Contents + { + get; + init; + } +} +// + diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/constructors.csproj b/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/constructors.csproj index 2150e3797ba5e..fd4bd08da2987 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/constructors.csproj +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/constructors/constructors.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/Program.cs b/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/Program.cs index 2e63e75ddd605..03f5f3bb4919e 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/Program.cs +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/Program.cs @@ -1,20 +1,19 @@ // public class Taxi { - public bool IsInitialized; + private string taxiTag; - public Taxi() - { - IsInitialized = true; - } + public Taxi(string tag) => taxiTag = tag; + + public override string ToString() => $"Taxi: {taxiTag}"; } class TestTaxi { static void Main() { - Taxi t = new Taxi(); - Console.WriteLine(t.IsInitialized); + Taxi t = new Taxi("Tag1345"); + Console.WriteLine(t); } } // @@ -37,16 +36,10 @@ public class Employee public Employee() { } // - public Employee(int annualSalary) - { - Salary = annualSalary; - } + public Employee(int annualSalary) => Salary = annualSalary; // - public Employee(int weeklySalary, int numberOfWeeks) - { - Salary = weeklySalary * numberOfWeeks; - } + public Employee(int weeklySalary, int numberOfWeeks) => Salary = weeklySalary * numberOfWeeks; } // diff --git a/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/UsingConstructors.csproj b/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/UsingConstructors.csproj index cb7c096a14515..ab3409ab367b7 100644 --- a/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/UsingConstructors.csproj +++ b/docs/csharp/programming-guide/classes-and-structs/snippets/using-constructors/UsingConstructors.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable UsingConstructors diff --git a/docs/csharp/programming-guide/classes-and-structs/using-constructors.md b/docs/csharp/programming-guide/classes-and-structs/using-constructors.md index e28c88aae29e5..917c90ff49b4f 100644 --- a/docs/csharp/programming-guide/classes-and-structs/using-constructors.md +++ b/docs/csharp/programming-guide/classes-and-structs/using-constructors.md @@ -1,19 +1,19 @@ --- title: "Using Constructors" description: This example shows how a class is instantiated by using the new operator in C#. The simple constructor is invoked after memory is allocated for the new object. -ms.date: 11/22/2024 +ms.date: 01/15/2025 helpviewer_keywords: - "constructors [C#], about constructors" --- -# Using Constructors (C# Programming Guide) +# Use constructors (C# programming guide) -When a [class](../../language-reference/keywords/class.md) or [struct](../../language-reference/builtin-types/struct.md) is instantiated, its constructor is called. Constructors have the same name as the class or struct, and they usually initialize the data members of the new object. +When a [class](../../language-reference/keywords/class.md) or [struct](../../language-reference/builtin-types/struct.md) is instantiated, the runtime calls its constructor. Constructors have the same name as the class or struct, and they usually initialize the data members of the new object. -In the following example, a class named `Taxi` is defined by using a simple constructor. This class is then instantiated with the [new](../../language-reference/operators/new-operator.md) operator. The `Taxi` constructor is invoked by the `new` operator immediately after memory is allocated for the new object. +In the following example, a class named `Taxi` is defined by using a simple constructor. This class is then instantiated with the [`new`](../../language-reference/operators/new-operator.md) operator. The runtime invokes the `Taxi` constructor immediately after memory is allocated for the new object. :::code source="./snippets/using-constructors/Program.cs" id="snippet1"::: -A constructor that takes no parameters is called a *parameterless constructor*. Parameterless constructors are invoked whenever an object is instantiated by using the `new` operator and no arguments are provided to `new`. C# 12 introduces *primary constructors*. A primary constructor specifies parameters that must be provided to initialize a new object. For more information, see [Instance Constructors](./instance-constructors.md). +A constructor that takes no parameters is called a *parameterless constructor*. The runtime invokes the parameterless constructor when an object is instantiated using the `new` operator and no arguments are provided to `new`. C# 12 introduced *primary constructors*. A primary constructor specifies parameters that must be provided to initialize a new object. For more information, see [Instance Constructors](./instance-constructors.md). Unless the class is [static](../../language-reference/keywords/static.md), classes without constructors are given a public parameterless constructor by the C# compiler in order to enable class instantiation. For more information, see [Static Classes and Static Class Members](./static-classes-and-static-class-members.md). @@ -23,7 +23,7 @@ You can prevent a class from being instantiated by making the constructor privat For more information, see [Private Constructors](./private-constructors.md). -Constructors for [struct](../../language-reference/builtin-types/struct.md) types resemble class constructors. When a struct type is instantiated with `new`, a constructor is invoked. When a `struct` is set to its `default` value, the runtime initializes all memory in the struct to 0. If you declare any field initializers in a `struct` type, you must supply a parameterless constructor. For more information, see the [Struct initialization and default values](../../language-reference/builtin-types/struct.md#struct-initialization-and-default-values) section of the [Structure types](../../language-reference/builtin-types/struct.md) article. +Constructors for [struct](../../language-reference/builtin-types/struct.md) types resemble class constructors. When a struct type is instantiated with `new`, the runtime invokes a constructor. When a `struct` is set to its `default` value, the runtime initializes all memory in the struct to 0. If you declare any field initializers in a `struct` type, you must supply a parameterless constructor. For more information, see the [Struct initialization and default values](../../language-reference/builtin-types/struct.md#struct-initialization-and-default-values) section of the [Structure types](../../language-reference/builtin-types/struct.md) article. The following code uses the parameterless constructor for , so that you're assured that the integer is initialized: @@ -32,14 +32,14 @@ int i = new int(); Console.WriteLine(i); ``` -The following code, however, causes a compiler error because it doesn't use `new`, and because it tries to use an object that hasn't been initialized: +The following code, however, causes a compiler error because it doesn't use `new`, and because it tries to use an object that isn't initialized: ```csharp int i; Console.WriteLine(i); ``` -Alternatively, objects based on `structs` (including all built-in numeric types) can be initialized or assigned and then used as in the following example: +Alternatively, some `struct` types (including all built-in numeric types) can be initialized or assigned and then used as in the following example: ```csharp int a = 44; // Initialize the value type... @@ -60,7 +60,7 @@ A constructor can use the `base` keyword to call the constructor of a base class :::code source="./snippets/using-constructors/Program.cs" id="snippet5"::: -In this example, the constructor for the base class is called before the block for the constructor is executed. The `base` keyword can be used with or without parameters. Any parameters to the constructor can be used as parameters to `base`, or as part of an expression. For more information, see [base](../../language-reference/keywords/base.md). +In this example, the constructor for the base class is called before the block for the constructor executes. The `base` keyword can be used with or without parameters. Any parameters to the constructor can be used as parameters to `base`, or as part of an expression. For more information, see [base](../../language-reference/keywords/base.md). In a derived class, if a base-class constructor isn't called explicitly by using the `base` keyword, the parameterless constructor, if there's one, is called implicitly. The following constructor declarations are effectively the same: @@ -70,17 +70,17 @@ In a derived class, if a base-class constructor isn't called explicitly by using If a base class doesn't offer a parameterless constructor, the derived class must make an explicit call to a base constructor by using `base`. -A constructor can invoke another constructor in the same object by using the [this](../../language-reference/keywords/this.md) keyword. Like `base`, `this` can be used with or without parameters, and any parameters in the constructor are available as parameters to `this`, or as part of an expression. For example, the second constructor in the previous example can be rewritten using `this`: +A constructor can invoke another constructor in the same object by using the [`this`](../../language-reference/keywords/this.md) keyword. Like `base`, `this` can be used with or without parameters, and any parameters in the constructor are available as parameters to `this`, or as part of an expression. For example, the second constructor in the previous example can be rewritten using `this`: :::code source="./snippets/using-constructors/Program.cs" id="snippet8"::: -The use of the `this` keyword in the previous example causes this constructor to be called: +The use of the `this` keyword in the previous example causes the following constructor to be called: :::code source="./snippets/using-constructors/Program.cs" id="snippet9"::: -Constructors can be marked as [public](../../language-reference/keywords/public.md), [private](../../language-reference/keywords/private.md), [protected](../../language-reference/keywords/protected.md), [internal](../../language-reference/keywords/internal.md), [protected internal](../../language-reference/keywords/protected-internal.md) or [private protected](../../language-reference/keywords/private-protected.md). These access modifiers define how users of the class can construct the class. For more information, see [Access Modifiers](./access-modifiers.md). +Constructors can be marked as [`public`](../../language-reference/keywords/public.md), [`private`](../../language-reference/keywords/private.md), [`protected`](../../language-reference/keywords/protected.md), [internal](../../language-reference/keywords/internal.md), [protected internal](../../language-reference/keywords/protected-internal.md) or [`private protected`](../../language-reference/keywords/private-protected.md). These access modifiers define how users of the class can construct the class. For more information, see [Access Modifiers](./access-modifiers.md). -A constructor can be declared static by using the [static](../../language-reference/keywords/static.md) keyword. Static constructors are called automatically, immediately before any static fields are accessed, and are used to initialize static class members. For more information, see [Static Constructors](./static-constructors.md). +A constructor can be declared static by using the [`static`](../../language-reference/keywords/static.md) keyword. Static constructors are called automatically, before any static fields are accessed, and are used to initialize static class members. For more information, see [Static Constructors](./static-constructors.md). ## C# Language Specification diff --git a/docs/csharp/tutorials/top-level-statements.md b/docs/csharp/tutorials/top-level-statements.md index a01cd945ab1a7..ca6738e43cbaf 100644 --- a/docs/csharp/tutorials/top-level-statements.md +++ b/docs/csharp/tutorials/top-level-statements.md @@ -1,7 +1,7 @@ --- title: Top-level statements tutorial description: This tutorial shows how you can use top-level statements to experiment and prove concepts while exploring your ideas -ms.date: 11/22/2024 +ms.date: 01/15/2025 --- # Tutorial: Explore ideas using top-level statements to build code as you learn @@ -47,7 +47,7 @@ Console.WriteLine("Hello, World!"); [!INCLUDE [csharp10-templates](../../../includes/csharp10-templates.md)] -This feature simplifies what's needed to begin exploring new ideas. You can use top-level statements for scripting scenarios, or to explore. Once you've got the basics working, you can start refactoring the code and create methods, classes, or other assemblies for reusable components you built. Top-level statements do enable quick experimentation and beginner tutorials. They also provide a smooth path from experimentation to full programs. +This feature simplifies your exploration of new ideas. You can use top-level statements for scripting scenarios, or to explore. Once you've got the basics working, you can start refactoring the code and create methods, classes, or other assemblies for reusable components you built. Top-level statements do enable quick experimentation and beginner tutorials. They also provide a smooth path from experimentation to full programs. Top-level statements are executed in the order they appear in the file. Top-level statements can only be used in one source file in your application. The compiler generates an error if you use them in more than one file. @@ -69,7 +69,7 @@ You can test your code by running the following `dotnet run` command: dotnet run -- Should I use top level statements in all my programs? ``` -The arguments after the `--` on the command line are passed to the program. You can see the type of the `args` variable, because that's printed to the console: +The arguments after the `--` on the command line are passed to the program. You can see the type of the `args` variable printed to the console: ```console System.String[] @@ -100,6 +100,8 @@ Should I use top level statements in all my programs? Better not tell you now. ``` +The code to generate an answer includes a variable declaration in your top level statements. The compiler includes that declaration in the compiler generated `Main` method. Because these variable declarations are local variables, you can't include the `static` modifier. + This code answers the questions, but let's add one more feature. You'd like your question app to simulate thinking about the answer. You can do that by adding a bit of ASCII animation, and pausing while working. Add the following code after the line that echoes the question: :::code language="csharp" source="snippets/top-level-statements/UtilitiesPassOne.cs" ID="AnimationFirstPass":::