Skip to content

Commit f831586

Browse files
add info about accessibility modifiers in constructors for F# records (#49028)
* add info for constructor accessibility modifiers * make changes from feedback * remove unnecessary semicolon * minor fixes * Update docs/fsharp/language-reference/records.md --------- Co-authored-by: Bill Wagner <[email protected]>
1 parent 58d2485 commit f831586

File tree

1 file changed

+74
-28
lines changed

1 file changed

+74
-28
lines changed

docs/fsharp/language-reference/records.md

Lines changed: 74 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,23 @@ ms.date: 12/21/2021
66
---
77
# Records (F#)
88

9-
Records represent simple aggregates of named values, optionally with members. They can either be structs or reference types. They are reference types by default.
9+
Records represent simple aggregates of named values, optionally with members. They can either be structs or reference types. They are reference types by default.
1010

1111
## Syntax
1212

1313
```fsharp
1414
[ attributes ]
1515
type [accessibility-modifier] typename =
16-
{ [ mutable ] label1 : type1;
17-
[ mutable ] label2 : type2;
18-
... }
16+
[accessibility-modifier] {
17+
[ mutable ] label1 : type1;
18+
[ mutable ] label2 : type2;
19+
...
20+
}
1921
[ member-list ]
2022
```
2123

24+
The `accessibility modifier` before the `typename` affects the visibility of the entire type, and is `public` by default. The second `accessibility modifier` only affects the constructor and fields.
25+
2226
## Remarks
2327

2428
In the previous syntax, *typename* is the name of the record type, *label1* and *label2* are names of values, referred to as *labels*, and *type1* and *type2* are the types of these values. *member-list* is the optional list of members for the type. You can use the `[<Struct>]` attribute to create a struct record rather than a record which is a reference type.
@@ -96,33 +100,33 @@ For example, the following code defines a `Person` and `Address` type as mutuall
96100
```fsharp
97101
// Create a Person type and use the Address type that is not defined
98102
type Person =
99-
{ Name: string
100-
Age: int
101-
Address: Address }
103+
{ Name: string
104+
Age: int
105+
Address: Address }
102106
// Define the Address type which is used in the Person record
103107
and Address =
104-
{ Line1: string
105-
Line2: string
106-
PostCode: string
107-
Occupant: Person }
108+
{ Line1: string
109+
Line2: string
110+
PostCode: string
111+
Occupant: Person }
108112
```
109113

110114
To create instances of both, you do the following:
111115

112116
```fsharp
113117
// Create a Person type and use the Address type that is not defined
114118
let rec person =
115-
{
116-
Name = "Person name"
117-
Age = 12
118-
Address =
119-
{
120-
Line1 = "line 1"
121-
Line2 = "line 2"
122-
PostCode = "abc123"
123-
Occupant = person
124-
}
125-
}
119+
{
120+
Name = "Person name"
121+
Age = 12
122+
Address =
123+
{
124+
Line1 = "line 1"
125+
Line2 = "line 2"
126+
PostCode = "abc123"
127+
Occupant = person
128+
}
129+
}
126130
```
127131

128132
If you were to define the previous example without the `and` keyword, then it would not compile. The `and` keyword is required for mutually recursive definitions.
@@ -147,9 +151,9 @@ You can specify members on records much like you can with classes. There is no s
147151

148152
```fsharp
149153
type Person =
150-
{ Name: string
151-
Age: int
152-
Address: string }
154+
{ Name: string
155+
Age: int
156+
Address: string }
153157
154158
static member Default =
155159
{ Name = "Phillip"
@@ -163,9 +167,9 @@ If you use a self identifier, that identifier refers to the instance of the reco
163167

164168
```fsharp
165169
type Person =
166-
{ Name: string
167-
Age: int
168-
Address: string }
170+
{ Name: string
171+
Age: int
172+
Address: string }
169173
170174
member this.WeirdToString() =
171175
this.Name + this.Address + string this.Age
@@ -174,6 +178,48 @@ let p = { Name = "a"; Age = 12; Address = "abc123" }
174178
let weirdString = p.WeirdToString()
175179
```
176180

181+
## Accessibility Modifiers on Records
182+
183+
The following code illustrates the use of accessibility modifiers. There are three files in the project: `Module1.fs`, `Test1.fs`, and `Test2.fs`. An internal record type and a record type with a private constructor are defined in Module1.
184+
185+
```fsharp
186+
// Module1.fs
187+
188+
module Module1
189+
190+
type internal internalRecd = { X: int }
191+
192+
type recdWithInternalCtor = private { Y: int }
193+
```
194+
195+
In the `Test1.fs` file, the internal record must be initialized with the `internal` access modifier, that's because the protection level of the value and the record must match, and both must belong to the same assembly.
196+
197+
```fsharp
198+
// Test1.fs
199+
200+
module Test1
201+
202+
open Module1
203+
204+
let myInternalRecd1 = { X = 2 } // This line will cause a compiler error.
205+
206+
let internal myInternalRecd2 = { X = 4 } // This is OK
207+
```
208+
209+
In the `Test2.fs` file, the record with the private constructor cannot be initialized directly due the protection level of the constructor.
210+
211+
```fsharp
212+
// Test2.fs
213+
214+
module Test2
215+
216+
open Module1
217+
218+
let myRecdWithInternalCtor = { Y = 6 } // This line will cause a compiler error.
219+
```
220+
221+
For more information about accessibility modifiers, see the [Access Control](./access-control.md) article.
222+
177223
## Differences Between Records and Classes
178224

179225
Record fields differ from class fields in that they are automatically exposed as properties, and they are used in the creation and copying of records. Record construction also differs from class construction. In a record type, you cannot define a constructor. Instead, the construction syntax described in this topic applies. Classes have no direct relationship between constructor parameters, fields, and properties.

0 commit comments

Comments
 (0)