Skip to content

Commit 005e122

Browse files
authored
Edit pass record reference (#27607)
* Fixes #27149 This first commit fixes #27149 In addition to the notes on that issues, emphasize the difference in the behavior of the synthesized parameterless constructor based on the presence of field initializers and other instance constructors. * Fixes #27154 Fix the description of positional parameters where you declare the property declaration in your source. * Fixes #27157 Reword sentence on `with` expressions and assignment. * Fixes #27158 Correctly point out that the string for each property is obtained by calling `ToString` on that property type. * Fixes #27171 Clarify that the property is `sealed` because the enclosing type is `sealed`. (The property declaration need not include the `sealed` modifier) * Fixes #27173 The method signature doesn't need the `sealed` modifier because the type is sealed. * Fixes #27174 Add motivation for sealing ToString in records. * update dates.
1 parent 708a948 commit 005e122

File tree

3 files changed

+20
-14
lines changed

3 files changed

+20
-14
lines changed

docs/csharp/language-reference/builtin-types/record.md

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Records - C# reference"
33
description: Learn about the record type in C#
4-
ms.date: 09/30/2021
4+
ms.date: 12/16/2021
55
f1_keywords:
66
- "record_CSharpKeyword"
77
helpviewer_keywords:
@@ -53,20 +53,20 @@ You can use positional parameters to declare properties of a record and to initi
5353

5454
When you use the positional syntax for property definition, the compiler creates:
5555

56-
* A public init-only auto-implemented property for each positional parameter provided in the record declaration.
57-
- For `record` types and `readonly record struct` types: An [init-only](../keywords/init.md) property can only be set in the constructor or by using a property initializer.
58-
- For `record struct` types: A read-write property that can be set in a constructor, property initializer, or assignment after construction.
56+
* A public auto-implemented property for each positional parameter provided in the record declaration.
57+
- For `record` types and `readonly record struct` types: An [init-only](../keywords/init.md) property.
58+
- For `record struct` types: A read-write property.
5959
* A primary constructor whose parameters match the positional parameters on the record declaration.
6060
* For record struct types, a parameterless constructor that sets each field to its default value.
61-
* A `Deconstruct` method with an `out` parameter for each positional parameter provided in the record declaration. This method is provided only if there are two or more positional parameters. The method deconstructs properties defined by using positional syntax; it ignores properties that are defined by using standard property syntax.
61+
* A `Deconstruct` method with an `out` parameter for each positional parameter provided in the record declaration. The method deconstructs properties defined by using positional syntax; it ignores properties that are defined by using standard property syntax.
6262

6363
You may want to add attributes to any of these elements the compiler creates from the record definition. You can add a *target* to any attribute you apply to the positional record's properties. The following example applies the <xref:System.Text.Json.Serialization.JsonPropertyNameAttribute?displayProperty=nameWithType> to each property of the `Person` record. The `property:` target indicates that the attribute is applied to the compiler-generated property. Other values are `field:` to apply the attribute to the field, and `param:` to apply the attribute to the parameter.
6464

6565
:::code language="csharp" source="snippets/shared/RecordType.cs" id="PositionalAttributes":::
6666

6767
The preceding example also shows how to create XML documentation comments for the record. You can add the `<param>` tag to add documentation for the primary constructor's parameters.
6868

69-
If the generated auto-implemented property definition isn't what you want, you can define your own property of the same name. If you do that, the generated constructor and deconstructor will use your property definition. For instance, the following example declares the `FirstName` and `LastName` properties of a positional record `public`, but restricts the `Id` positional parameter to `internal`. You can use this syntax for records and record struct types. You must add the explicit assignment of the declared property to its corresponding positional parameter.
69+
If the generated auto-implemented property definition isn't what you want, you can define your own property of the same name. For example, you may want to change accessibility or mutability, or provide an implementation for either the `get` or `set` accessor. If you declare the property in your source, you must initialize it from the positional parameter of the record. The generated deconstructor will use your property definition. For instance, the following example declares the `FirstName` and `LastName` properties of a positional record `public`, but restricts the `Id` positional parameter to `internal`. You can use this syntax for records and record struct types.
7070

7171
:::code language="csharp" source="snippets/shared/RecordType.cs" id="PositionalWithManualProperty":::
7272

@@ -133,7 +133,7 @@ The result of a `with` expression is a *shallow copy*, which means that for a re
133133

134134
To implement this feature for `record class` types, the compiler synthesizes a clone method and a copy constructor. The virtual clone method returns a new record initialized by the copy constructor. When you use a `with` expression, the compiler creates code that calls the clone method and then sets the properties that are specified in the `with` expression.
135135

136-
If you need different copying behavior, you can write your own copy constructor in a `record class`. If you do that, the compiler won't synthesize one. Make your constructor `private` if the record is `sealed`, otherwise make it `protected`. The compiler doesn't synthesize a copy constructor for `record struct` types. You can write one, but the compiler won't generate calls to it for `with` expressions. Instead, the compiler uses assignment.
136+
If you need different copying behavior, you can write your own copy constructor in a `record class`. If you do that, the compiler won't synthesize one. Make your constructor `private` if the record is `sealed`, otherwise make it `protected`. The compiler doesn't synthesize a copy constructor for `record struct` types. You can write one, but the compiler won't generate calls to it for `with` expressions. The values of the `record struct` are copied on assignment.
137137

138138
You can't override the clone method, and you can't create a member named `Clone` in any record type. The actual name of the clone method is compiler-generated.
139139

@@ -143,7 +143,7 @@ Record types have a compiler-generated <xref:System.Object.ToString%2A> method t
143143

144144
> \<record type name> { \<property name> = \<value>, \<property name> = \<value>, ...}
145145
146-
For reference types, the type name of the object that the property refers to is displayed instead of the property value. In the following example, the array is a reference type, so `System.String[]` is displayed instead of the actual array element values:
146+
The string printed for `<value>` is the string returned by the <xref:System.Object.ToString> for the type of the property. In the following example, `ChildNames`is a <xref:System.Array?displayProperty=nameWithType>, where `ToString` returns `System.String[]`:
147147

148148
```
149149
Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }
@@ -154,7 +154,7 @@ The `ToString` override creates a <xref:System.Text.StringBuilder> object with t
154154

155155
:::code language="csharp" source="snippets/shared/RecordType.cs" id="ToStringOverrideDefault":::
156156

157-
You can provide your own implementation of `PrintMembers` or the `ToString` override. Examples are provided in the [`PrintMembers` formatting in derived records](#printmembers-formatting-in-derived-records) section later in this article. In C# 10 and later, your implementation of `ToString` may include the `sealed` modifier, which prevents the compiler from synthesizing a `ToString` implementation for any derived records. Effectively, that means the `ToString` output won't include the runtime type information. (All members and values are displayed, because derived records will still have a PrintMembers method generated.)
157+
You can provide your own implementation of `PrintMembers` or the `ToString` override. Examples are provided in the [`PrintMembers` formatting in derived records](#printmembers-formatting-in-derived-records) section later in this article. In C# 10 and later, your implementation of `ToString` may include the `sealed` modifier, which prevents the compiler from synthesizing a `ToString` implementation for any derived records. You can do this to create a consistent string representation throughout a hierarchy of `record` types. (Derived records will still have a `PrintMembers` method generated for all derived properties.)
158158

159159
## Inheritance
160160

@@ -178,7 +178,7 @@ This section applies to `record class` types, but not `record struct` types. For
178178

179179
In the example, all variables are declared as `Person`, even when the instance is a derived type of either `Student` or `Teacher`. The instances have the same properties and the same property values. But `student == teacher` returns `False` although both are `Person`-type variables, and `student == student2` returns `True` although one is a `Person` variable and one is a `Student` variable. The equality test depends on the runtime type of the actual object, not the declared type of the variable.
180180

181-
To implement this behavior, the compiler synthesizes an `EqualityContract` property that returns a <xref:System.Type> object that matches the type of the record. The `EqualityContract` enables the equality methods to compare the runtime type of objects when they're checking for equality. If the base type of a record is `object`, this property is `virtual`. If the base type is another record type, this property is an override. If the record type is `sealed`, this property is `sealed`.
181+
To implement this behavior, the compiler synthesizes an `EqualityContract` property that returns a <xref:System.Type> object that matches the type of the record. The `EqualityContract` enables the equality methods to compare the runtime type of objects when they're checking for equality. If the base type of a record is `object`, this property is `virtual`. If the base type is another record type, this property is an override. If the record type is `sealed`, this property is effectively `sealed` because the type is `sealed`.
182182

183183
When comparing two instances of a derived type, the synthesized equality methods check all properties of the base and derived types for equality. The synthesized `GetHashCode` method uses the `GetHashCode` method from all properties and fields declared in the base type and the derived record type.
184184

@@ -197,7 +197,7 @@ The synthesized `PrintMembers` method of a derived record type calls the base im
197197
You can provide your own implementation of the `PrintMembers` method. If you do that, use the following signature:
198198

199199
* For a `sealed` record that derives from `object` (doesn't declare a base record): `private bool PrintMembers(StringBuilder builder)`;
200-
* For a `sealed` record that derives from another record: `protected sealed override bool PrintMembers(StringBuilder builder)`;
200+
* For a `sealed` record that derives from another record (note that the enclosing type is `sealed`, so the method is effectively `sealed`): `protected override bool PrintMembers(StringBuilder builder)`;
201201
* For a record that isn't `sealed` and derives from object: `protected virtual bool PrintMembers(StringBuilder builder);`
202202
* For a record that isn't `sealed` and derives from another record: `protected override bool PrintMembers(StringBuilder builder);`
203203

@@ -206,7 +206,7 @@ Here's an example of code that replaces the synthesized `PrintMembers` methods,
206206
:::code language="csharp" source="snippets/shared/RecordType.cs" id="PrintMembersImplementation":::
207207

208208
> [!NOTE]
209-
> In C# 10 and later, the compiler will synthesize `PrintMembers` when a base record has sealed the `ToString` method. You can also create your own implementation of `PrintMembers`.
209+
> In C# 10 and later, the compiler will synthesize `PrintMembers` in derived records even when a base record has sealed the `ToString` method. You can also create your own implementation of `PrintMembers`.
210210
211211
### Deconstructor behavior in derived records
212212

docs/csharp/language-reference/builtin-types/snippets/shared/StructType.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,10 @@ public static void Main()
199199

200200
var m2 = new Measurement();
201201
Console.WriteLine(m2); // output: 0 ()
202-
}
202+
203+
var m3 = default(Measurement);
204+
Console.WriteLine(m3); // output: 0 ()
205+
}
203206
// </FieldInitializer>
204207
}
205208
}

docs/csharp/language-reference/builtin-types/struct.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Structure types - C# reference"
33
description: Learn about the struct type in C#
4-
ms.date: 09/15/2021
4+
ms.date: 12/16/2021
55
f1_keywords:
66
- "struct_CSharpKeyword"
77
helpviewer_keywords:
@@ -118,6 +118,9 @@ If you don't declare a parameterless constructor explicitly, a structure type pr
118118

119119
As the preceding example shows, the default value expression and array instantiation ignore field initializers.
120120

121+
> [!IMPORTANT]
122+
> When a `struct` includes field initializers, but doesn't include any explicit instance constructors, the synthesized public parameterless constructor performs the specified field initializers. If that `struct` includes an explicit instance constructor, the synthesized parameterless constructor produces the same value as the `default` expression.
123+
121124
For more information, see the [Parameterless struct constructors](~/_csharplang/proposals/csharp-10.0/parameterless-struct-constructors.md) feature proposal note.
122125

123126
## Instantiation of a structure type

0 commit comments

Comments
 (0)