107 lines
No EOL
4 KiB
Markdown
107 lines
No EOL
4 KiB
Markdown
|
|
# C# Language Design Meeting for May 11, 2020
|
|
|
|
## Agenda
|
|
|
|
Records
|
|
|
|
## Discussion
|
|
|
|
Today we tried to resolve some of the biggest questions about records,
|
|
namely how to unify the semantics of nominal records and positional
|
|
records, and what are the key scenarios that we are trying to resolve
|
|
with records.
|
|
|
|
The main inconsistency is that the members `record class Person(string FirstName, string
|
|
LastName)` are very different from the members in `class Person(string firstName, string
|
|
lastName)`. One way of resolving this is to unify the meaning of the declaration in the direction
|
|
of primary constructors. In this variant, the parameters of a primary constructor always capture
|
|
items by default.
|
|
|
|
To produce public init-only properties like we were exploring, we would require an extra
|
|
keyword, `data`, that could be generalizable. So a record which has two public init-only
|
|
members would be written
|
|
|
|
```C#
|
|
record Person(data string FirstName, data string LastName);
|
|
```
|
|
|
|
This would allow a generalizable `data` keyword that could be applied even in regular
|
|
classes, e.g.
|
|
|
|
```C#
|
|
class Person
|
|
{
|
|
data string FirstName;
|
|
data string LastName;
|
|
public Person(string first, string last)
|
|
{
|
|
|
|
}
|
|
}
|
|
```
|
|
|
|
The worry here is that we're harming an essential motivation for records, namely a short syntax
|
|
for immutable data types. In the above syntax, `record` alone does not mean immutable data type,
|
|
but instead only value equality and non-destructive mutation. A problem with this is that value
|
|
equality is dangerous for mutable classes, since the hash code can change after being added to a
|
|
dictionary. This was why we were previously cautious about adding a general feature for value
|
|
equality. One option to discourage misuse would be to provide a warning for any non-immutable
|
|
member in a record class.
|
|
|
|
The other problem is, frankly, it's not that short. Aside from some duplication of intent by
|
|
requiring both `record` and `data` modifiers, it also requires applying the `data` modifier to
|
|
each member, so the overhead grows larger as the type does.
|
|
|
|
Alternatively, we could go in the complete opposite direction: limit customizability by making
|
|
records all about public immutable data.
|
|
|
|
For instance, nominal records could also have syntax abbreviation
|
|
|
|
```C#
|
|
record Person { string FirstName; string LastName; }
|
|
```
|
|
|
|
and we could avoid confusion by prohibiting other members entirely.
|
|
|
|
This would look at lot more like positional records, e.g.
|
|
|
|
```C#
|
|
record Person(string FirstName, string LastName);
|
|
```
|
|
|
|
and we could introduce further restrictions on those by also disallowing other members
|
|
in the body, or even disallowing primary constructors entirely.
|
|
|
|
Disallowing all members inside of records is draconian, but not entirely without precedence.
|
|
Enums work the same way in C# and members are added via extensions methods. That's not a ringing
|
|
endorsement since we've considered proposals for allowing members in enums before, but it also
|
|
doesn't put it outside the realm of possibility for C#, especially in earlier forms.
|
|
|
|
The main drawback of the simplest form is the risk that we might have trouble evolving the
|
|
feature to fit all circumstances. If we wanted to allow a user to define private fields, the
|
|
syntax with no accessibility modifier now means "public init-only property" so we might not
|
|
be able to add support for private fields at all, or we might have to use a syntactic distinction
|
|
that requires a `private` accessibility, which is a subtle change.
|
|
|
|
### Conclusion
|
|
|
|
We largely prefer the short syntax for records. A nominal record would look like
|
|
|
|
```C#
|
|
record class Person { string FirstName; string LastName; }
|
|
```
|
|
|
|
This would create a class with public `init-only` properties named `FirstName`
|
|
and `LastName`, along with equality and non-destructive mutation methods.
|
|
|
|
Similarly,
|
|
|
|
```C#
|
|
record class Person(string FirstName, string LastName);
|
|
```
|
|
|
|
would create a class with all of the above, but also a constructor and Deconstruct.
|
|
|
|
We have yet to confirm whether `record` disallows private fields entirely, or if it
|
|
just changes the default accessibility. |