2020-05-31 10:04:19 +02:00
|
|
|
|
|
|
|
# C# Language Design Meeting for May 27, 2020
|
|
|
|
|
|
|
|
## Agenda
|
|
|
|
|
|
|
|
1. Records -- syntax
|
|
|
|
|
|
|
|
## Discussion
|
|
|
|
|
|
|
|
### Syntax Questions
|
|
|
|
|
|
|
|
We got significant feedback that `record` is a better name than `data` for indicating
|
|
|
|
a `record`. There are two syntaxes we've been considering here:
|
|
|
|
|
|
|
|
1. `record class Person { ... }`
|
|
|
|
2. `record Person { ... }`
|
|
|
|
|
|
|
|
The main difference here is that (2) has less obvious space for a `struct` token, which
|
|
|
|
raises the question of whether "record structs" are a feature we want to enable.
|
|
|
|
|
|
|
|
There are a couple arguments for why records would be useful for structs. The first is
|
|
|
|
that "value behavior" is a general feature that could be useful for both structs and classes.
|
|
|
|
Value equality exists for structs today, but it is potentially slow in the runtime implementation.
|
|
|
|
|
|
|
|
The second is that the syntax provided for classes is also useful for structs. The positional
|
|
|
|
syntax specifically seems attractive because it has a lot of similarity to tuples and allows a
|
|
|
|
form of "named tuple."
|
|
|
|
|
|
|
|
```C#
|
|
|
|
record struct Point(int X, int Y);
|
|
|
|
```
|
|
|
|
|
|
|
|
On the other hand, we could improve the performance of equality, completely separate from records.
|
|
|
|
For instance, the compiler could add equality methods if they are not present. We also do not necessarily
|
|
|
|
need to address structs first. Since structs already have many features of records they are, in a sense,
|
2020-09-11 03:56:10 +02:00
|
|
|
"less far behind" than classes in record features. It makes sense to concentrate first on classes and
|
2020-05-31 10:04:19 +02:00
|
|
|
consider augmentations for structs in a future update.
|
|
|
|
|
|
|
|
So to return to the original question, we have to decide if we want to move forward with option (2), which
|
|
|
|
is a new form of declaration. Notably, this is a breaking change for certain scenarios e.g.,
|
|
|
|
|
|
|
|
```C#
|
|
|
|
record M(int X, int Y) { ... }
|
|
|
|
|
|
|
|
class C
|
|
|
|
{
|
|
|
|
record M(int X, int Y) { ... }
|
|
|
|
partial record M(int X, int Y);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
All of these are currently method declaration syntax. In C# 9 this would be a record declaration
|
|
|
|
with a positional constructor and a body. Normally we would never consider this kind of change,
|
|
|
|
but since we started shipping with .NET Core we do not automatically upgrade language version
|
|
|
|
unless the target framework is the newest one (i.e., NET 5).
|
|
|
|
|
|
|
|
#### Conclusion
|
|
|
|
|
|
|
|
Do not support structs for now. They already support many features of records and we can add
|
|
|
|
more, time permitting.
|
|
|
|
|
|
|
|
The accepted proposal is that the syntax, `<modifiers> <attributes> 'record'` followed by
|
|
|
|
`identifier` and either '(', '{', or '<' would be contextually parsed as a record declaration
|
|
|
|
only if the language version is C# 9.
|
|
|
|
|
|
|
|
### Short-property syntax
|
|
|
|
|
|
|
|
We previously agreed that, to unify the syntax forms in the positional and nominal declaration, we
|
|
|
|
would allow fields in nominal records with no modifiers to instead be interpreted as public auto-properties.
|
|
|
|
After looking at feedback and exploring some of the related issues, we've decided that's not the best approach.
|
|
|
|
|
|
|
|
There are a few proposals on the table:
|
|
|
|
|
|
|
|
1. Leave positional records the same, do not provide special syntax for nominal records.
|
|
|
|
|
|
|
|
```C#
|
|
|
|
public record Point(int X, int Y);
|
|
|
|
public record Point
|
|
|
|
{
|
|
|
|
public int X { get; init; }
|
|
|
|
public int Y { get; init; }
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
2. Unify the declaration forms in favor of nominal records, allowing property declarations in the
|
|
|
|
record parameter list
|
|
|
|
|
|
|
|
```C#
|
|
|
|
public record Point(
|
|
|
|
public int X { get; init; },
|
|
|
|
public int Y { get; init; }
|
|
|
|
)
|
|
|
|
public record Point
|
|
|
|
{
|
|
|
|
public int X { get; init; },
|
|
|
|
public int Y { get; init; }
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
3. Keep positional records the same, provide a new modifier (e.g., `data` or `init`) for members
|
|
|
|
which means "public init-only property"
|
|
|
|
|
|
|
|
```C#
|
|
|
|
public record Point(int X, int Y);
|
|
|
|
public record Point
|
|
|
|
{
|
|
|
|
data int X;
|
|
|
|
data int Y;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
4. Provide the new modifier from (3), and require it in both types of records
|
|
|
|
|
|
|
|
```C#
|
|
|
|
public record Point(data int X, data int Y);
|
|
|
|
public record Point
|
|
|
|
{
|
|
|
|
data int X;
|
|
|
|
data int Y;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
After discussion, we prefer (3). Positional records already seem to have enough syntactic distinction and
|
|
|
|
the `data` keywords seem superfluous in this position. It also makes the shortest syntax form match up
|
|
|
|
with the most common use case.
|
|
|
|
|
|
|
|
However, we do think some further keyword is necessary for nominal records. Looking too much like existing
|
|
|
|
fields seems like it would be too confusing, especially if we want to also allow fields in records.
|
|
|
|
|
|
|
|
Instead, we're considering leaving positional records to generate public auto-properties by default, partly
|
|
|
|
because they are already a significantly different syntax that cannot be confused with existing language
|
|
|
|
constructs, and providing a new mechanism for positional records.
|
|
|
|
|
|
|
|
#### Conclusion
|
|
|
|
|
|
|
|
Keep positional records the same, provide a `data` modifier for fields which means "public
|
|
|
|
init-only property"
|
|
|
|
|
|
|
|
```C#
|
|
|
|
public record Point(int X, int Y);
|
|
|
|
public record Point
|
|
|
|
{
|
|
|
|
data int X;
|
|
|
|
data int Y;
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|