Skip to content

Commit f4c9cfb

Browse files
BillWagnergewarren
andauthored
Clarify that positional parameters can create fields (#44691)
* Clarify that positional parameters can create fields Record types with positional parameters can synthesize fields as well as parameters. Make that more clear. * fix warnings * Update docs/csharp/language-reference/builtin-types/record.md Co-authored-by: Genevieve Warren <[email protected]> * respond to feedback --------- Co-authored-by: Genevieve Warren <[email protected]>
1 parent 59d6b4d commit f4c9cfb

File tree

10 files changed

+41
-17
lines changed

10 files changed

+41
-17
lines changed

docs/csharp/fundamentals/coding-style/identifier-names.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ public record PhysicalAddress(
123123
string ZipCode);
124124
```
125125

126-
For more information on positional records, see [Positional syntax for property definition](../../language-reference/builtin-types/record.md#positional-syntax-for-property-definition).
126+
For more information on positional records, see [Positional syntax for property definition](../../language-reference/builtin-types/record.md#positional-syntax-for-property-and-field-definition).
127127

128128
### Camel case
129129

docs/csharp/fundamentals/functional/deconstruct.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ Some system types provide the `Deconstruct` method as a convenience. For example
102102

103103
## `record` types
104104

105-
When you declare a [record](../../language-reference/builtin-types/record.md) type by using two or more positional parameters, the compiler creates a `Deconstruct` method with an `out` parameter for each positional parameter in the `record` declaration. For more information, see [Positional syntax for property definition](../../language-reference/builtin-types/record.md#positional-syntax-for-property-definition) and [Deconstructor behavior in derived records](../../language-reference/builtin-types/record.md#deconstructor-behavior-in-derived-records).
105+
When you declare a [record](../../language-reference/builtin-types/record.md) type by using two or more positional parameters, the compiler creates a `Deconstruct` method with an `out` parameter for each positional parameter in the `record` declaration. For more information, see [Positional syntax for property definition](../../language-reference/builtin-types/record.md#positional-syntax-for-property-and-field-definition) and [Deconstructor behavior in derived records](../../language-reference/builtin-types/record.md#deconstructor-behavior-in-derived-records).
106106

107107
## See also
108108

docs/csharp/fundamentals/types/records.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Immutability isn't appropriate for all data scenarios. [Entity Framework Core](/
3333

3434
The same syntax that [declares](classes.md#declaring-classes) and [instantiates](classes.md#creating-objects) classes or structs can be used with records. Just substitute the `class` keyword with the `record`, or use `record struct` instead of `struct`. Likewise, the same syntax for expressing inheritance relationships is supported by record classes. Records differ from classes in the following ways:
3535

36-
* You can use [positional parameters](../../language-reference/builtin-types/record.md#positional-syntax-for-property-definition) in a [primary constructor](../../programming-guide/classes-and-structs/instance-constructors.md#primary-constructors) to create and instantiate a type with immutable properties.
36+
* You can use [positional parameters](../../language-reference/builtin-types/record.md#positional-syntax-for-property-and-field-definition) in a [primary constructor](../../programming-guide/classes-and-structs/instance-constructors.md#primary-constructors) to create and instantiate a type with immutable properties.
3737
* The same methods and operators that indicate reference equality or inequality in classes (such as <xref:System.Object.Equals(System.Object)?displayProperty=nameWithType> and `==`), indicate [value equality or inequality](../../language-reference/builtin-types/record.md#value-equality) in records.
3838
* You can use a [`with` expression](../../language-reference/builtin-types/record.md#nondestructive-mutation) to create a copy of an immutable object with new values in selected properties.
3939
* A record's `ToString` method creates a formatted string that shows an object's type name and the names and values of all its public properties.

docs/csharp/language-reference/attributes/general.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ If <xref:System.AttributeUsageAttribute.Inherited> is `false`, then derived clas
129129

130130
In this case, `NonInheritedAttribute` isn't applied to `DClass` via inheritance.
131131

132-
You can also use these keywords to specify where an attribute should be applied. For example, you can use the `field:` specifier to add an attribute to the backing field of an [automatically implemented property](../../programming-guide/classes-and-structs/properties.md#automatically-implemented-properties). Or you can use the `field:`, `property:` or `param:` specifier to apply an attribute to any of the elements generated from a positional record. For an example, see [Positional syntax for property definition](../builtin-types/record.md#positional-syntax-for-property-definition).
132+
You can also use these keywords to specify where an attribute should be applied. For example, you can use the `field:` specifier to add an attribute to the backing field of an [automatically implemented property](../../programming-guide/classes-and-structs/properties.md#automatically-implemented-properties). Or you can use the `field:`, `property:` or `param:` specifier to apply an attribute to any of the elements generated from a positional record. For an example, see [Positional syntax for property definition](../builtin-types/record.md#positional-syntax-for-property-and-field-definition).
133133

134134
## `AsyncMethodBuilder` attribute
135135

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

+13-9
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
22
title: "Records"
3-
description: Learn about the record type in C#
4-
ms.date: 11/22/2023
3+
description: Learn about the record modifier for class and struct types in C#. Records provide standard support for value based equality on instances of record types.
4+
ms.date: 02/05/2025
55
f1_keywords:
66
- "record_CSharpKeyword"
77
helpviewer_keywords:
@@ -35,7 +35,7 @@ Record structs can be mutable as well, both positional record structs and record
3535

3636
While records can be mutable, they're primarily intended for supporting immutable data models. The record type offers the following features:
3737

38-
* [Concise syntax for creating a reference type with immutable properties](#positional-syntax-for-property-definition)
38+
* [Concise syntax for creating a reference type with immutable properties](#positional-syntax-for-property-and-field-definition)
3939
* Built-in behavior useful for a data-centric reference type:
4040
* [Value equality](#value-equality)
4141
* [Concise syntax for nondestructive mutation](#nondestructive-mutation)
@@ -49,9 +49,9 @@ The preceding examples show some distinctions between records that are reference
4949

5050
The remainder of this article discusses both `record class` and `record struct` types. The differences are detailed in each section. You should decide between a `record class` and a `record struct` similar to deciding between a `class` and a `struct`. The term *record* is used to describe behavior that applies to all record types. Either `record struct` or `record class` is used to describe behavior that applies to only struct or class types, respectively.
5151

52-
## Positional syntax for property definition
52+
## Positional syntax for property and field definition
5353

54-
You can use positional parameters to declare properties of a record and to initialize the property values when you create an instance:
54+
You can use positional parameters to declare properties of a record or to initialize property or field values. The following example creates a record with two positional properties:
5555

5656
:::code language="csharp" source="snippets/shared/RecordType.cs" id="InstantiatePositional":::
5757

@@ -70,19 +70,23 @@ You might want to add attributes to any of these elements the compiler creates f
7070

7171
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.
7272

73-
If the generated automatically implemented property definition isn't what you want, you can define your own property of the same name. For example, you might 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. If your property is an automatically implemented property, you must initialize the property. If you add a backing field in your source, you must initialize the backing field. The generated deconstructor uses 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.
73+
If the generated automatically implemented property definition isn't what you want, you can define your own property or field of the same name. For example, you might want to change accessibility or mutability, or provide an implementation for either the `get` or `set` accessor. If you declare the member in your source, you must initialize it from the positional parameter of the record. If your property is an automatically implemented property, you must initialize the property. If you add a backing field in your source, you must initialize the backing field. The generated deconstructor uses your property or field 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.
7474

7575
:::code language="csharp" source="snippets/shared/RecordType.cs" id="PositionalWithManualProperty":::
7676

77+
If you want to create a field instead of a property, assign the positional parameter to a field, as shown in the following example:
78+
79+
:::code language="csharp" source="snippets/shared/RecordType.cs" id="PositionalWithManualField":::
80+
7781
A record type doesn't have to declare any positional properties. You can declare a record without any positional properties, and you can declare other fields and properties, as in the following example:
7882

7983
:::code language="csharp" source="snippets/shared/RecordType.cs" id="MixedSyntax":::
8084

81-
If you define properties by using standard property syntax but omit the access modifier, the properties are implicitly `private`.
85+
Properties that the compiler generates from positional parameters are `public`. You declare the access modifiers on any properties you explicitly declare.
8286

8387
## Immutability
8488

85-
A *positional record* and a *positional readonly record struct* declare init-only properties. A *positional record struct* declares read-write properties. You can override either of those defaults, as shown in the previous section.
89+
A *positional record class* and a *positional readonly record struct* declare init-only properties. A *positional record struct* declares read-write properties. You can override either of those defaults, as shown in the previous section.
8690

8791
Immutability can be useful when you need a data-centric type to be thread-safe or you're depending on a hash code remaining the same in a hash table. Immutability isn't appropriate for all data scenarios, however. [Entity Framework Core](/ef/core/), for example, doesn't support updating with immutable entity types.
8892

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

213217
> [!NOTE]
214-
> 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`.
218+
> The compiler synthesizes `PrintMembers` in derived records even when a base record sealed the `ToString` method. You can also create your own implementation of `PrintMembers`.
215219
216220
### Deconstructor behavior in derived records
217221

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

+20
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,26 @@ public static void Main()
145145
}
146146
}
147147

148+
namespace positionalwithmanualfield
149+
{
150+
public static class Example
151+
{
152+
// <PositionalWithManualField>
153+
public record Person(string FirstName, string LastName, string Id)
154+
{
155+
internal readonly string Id = Id; // this.Id set to parameter Id
156+
}
157+
158+
public static void Main()
159+
{
160+
Person person = new("Nancy", "Davolio", "12345");
161+
Console.WriteLine(person.FirstName); //output: Nancy
162+
163+
}
164+
// </PositionalWithManualField>
165+
}
166+
}
167+
148168
namespace shallowimmutability
149169
{
150170
public static class Example

docs/csharp/language-reference/keywords/required.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ The `required` modifier indicates that the *field* or *property* it's applied to
1919
- A type with any `required` members may not be used as a type argument when the type parameter includes the `new()` constraint. The compiler can't enforce that all required members are initialized in the generic code.
2020
- The `required` modifier isn't allowed on the declaration for positional parameters on a record. You can add an explicit declaration for a positional property that does include the `required` modifier.
2121

22-
Some types, such as [positional records](../builtin-types/record.md#positional-syntax-for-property-definition), use a primary constructor to initialize positional properties. If any of those properties include the `required` modifier, the primary constructor adds the [`SetsRequiredMembers`](../attributes/general.md#setsrequiredmembers-attribute) attribute. This indicates that the primary constructor initializes all required members. You can write your own constructor with the <xref:System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute?displayProperty=nameWithType> attribute. However, the compiler doesn't verify that these constructors do initialize all required members. Rather, the attribute asserts to the compiler that the constructor does initialize all required members. The `SetsRequiredMembers` attribute adds these rules to constructors:
22+
Some types, such as [positional records](../builtin-types/record.md#positional-syntax-for-property-and-field-definition), use a primary constructor to initialize positional properties. If any of those properties include the `required` modifier, the primary constructor adds the [`SetsRequiredMembers`](../attributes/general.md#setsrequiredmembers-attribute) attribute. This indicates that the primary constructor initializes all required members. You can write your own constructor with the <xref:System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute?displayProperty=nameWithType> attribute. However, the compiler doesn't verify that these constructors do initialize all required members. Rather, the attribute asserts to the compiler that the constructor does initialize all required members. The `SetsRequiredMembers` attribute adds these rules to constructors:
2323

2424
- A constructor that chains to another constructor annotated with the `SetsRequiredMembers` attribute, either `this()`, or `base()`, must also include the `SetsRequiredMembers` attribute. That ensures that callers can correctly use all appropriate constructors.
2525
- Copy constructors generated for `record` types have the `SetsRequiredMembers` attribute applied if any of the members are `required`.

docs/csharp/language-reference/operators/deconstruction.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ In the preceding example, the `Y` and `label` members are discarded. You can spe
2323

2424
## Record deconstruction
2525

26-
[Record](../builtin-types/record.md) types that have a [primary constructor](../builtin-types/record.md#positional-syntax-for-property-definition) support deconstruction for positional parameters. The compiler synthesizes a `Deconstruct` method that extracts the properties synthesized from positional parameters in the primary constructor. The compiler-synthesized `Deconstruction` method doesn't extract properties declared as properties in the record type.
26+
[Record](../builtin-types/record.md) types that have a [primary constructor](../builtin-types/record.md#positional-syntax-for-property-and-field-definition) support deconstruction for positional parameters. The compiler synthesizes a `Deconstruct` method that extracts the properties synthesized from positional parameters in the primary constructor. The compiler-synthesized `Deconstruction` method doesn't extract properties declared as properties in the record type.
2727

2828
The `record` shown in the following code declares two positional properties, `SquareFeet` and `Address`, along with another property, `RealtorNotes`:
2929

docs/csharp/language-reference/operators/patterns.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ You can also extend a positional pattern in any of the following ways:
226226

227227
:::code language="csharp" source="snippets/patterns/PositionalPattern.cs" id="WithTypeCheck":::
228228

229-
The preceding example uses [positional records](../builtin-types/record.md#positional-syntax-for-property-definition) that implicitly provide the `Deconstruct` method.
229+
The preceding example uses [positional records](../builtin-types/record.md#positional-syntax-for-property-and-field-definition) that implicitly provide the `Deconstruct` method.
230230

231231
- Use a [property pattern](#property-pattern) within a positional pattern, as the following example shows:
232232

docs/standard/serialization/system-text-json/immutability.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Records are also supported for both serialization and deserialization, as shown
4444

4545
:::code language="csharp" source="snippets/how-to-contd/csharp/Records.cs":::
4646

47-
You can apply any of the attributes to the property names, using the `property:` target on the attribute. For more information on positional records, see the article on [records](../../../csharp/language-reference/builtin-types/record.md#positional-syntax-for-property-definition) in the C# language reference.
47+
You can apply any of the attributes to the property names, using the `property:` target on the attribute. For more information on positional records, see the article on [records](../../../csharp/language-reference/builtin-types/record.md#positional-syntax-for-property-and-field-definition) in the C# language reference.
4848

4949
## Non-public members and property accessors
5050

0 commit comments

Comments
 (0)