2020-10-08 00:57:37 +02:00
|
|
|
# C# Language Design Meeting for October 7th, 2020
|
|
|
|
|
|
|
|
## Agenda
|
|
|
|
|
|
|
|
1. [`record struct` syntax](#record-struct-syntax)
|
|
|
|
2. [`data` members redux](#data-members-redux)
|
2020-10-08 01:01:51 +02:00
|
|
|
3. [`ReadOnlySpan<char>` patterns](#readonlyspanchar-patterns)
|
2020-10-08 00:57:37 +02:00
|
|
|
|
|
|
|
## Quote of the Day
|
|
|
|
|
|
|
|
- "And we're almost there (famous last words, I'll knock on something)"
|
|
|
|
- "What about `record delegate`... does `record interface` make sense... I'll go away now"
|
|
|
|
|
|
|
|
## Discussion
|
|
|
|
|
|
|
|
### `record struct` syntax
|
|
|
|
|
|
|
|
First, we looked at what syntax we would use to specify `struct` types that are records. There are two possibilities:
|
|
|
|
|
|
|
|
```cs
|
|
|
|
record struct Person;
|
|
|
|
struct record Person;
|
|
|
|
```
|
|
|
|
|
|
|
|
In the former, `record` is the modifier on a `struct` type. In the latter, `struct` is the modifier on `record` types.
|
|
|
|
We also considered whether to allow `record class`.
|
|
|
|
|
|
|
|
#### Conclusion
|
|
|
|
|
|
|
|
By unanimous agreement, `record struct` is the preferred syntax, and `record class` is allowed. `record class` is
|
|
|
|
synonymous with just `record`.
|
|
|
|
|
|
|
|
### `data` members redux
|
|
|
|
|
|
|
|
With `data`, we come back to the same question we had at the end of [Monday](LDM-2020-10-05.md#data-members): should we have
|
|
|
|
`data` members, and if we do, what should they mean. `data` can be viewed as a strategy to try and get nominal records in a
|
|
|
|
single line, much like positional records. This is not a goal of brevity just for brevity's sake: in discriminated unions,
|
|
|
|
listing many variants as nominal records is a design goal, and single-line declarations are particularly useful for this case.
|
|
|
|
Many languages with discriminated unions based on inheritance introduce short class-declaration syntax for use in union
|
|
|
|
declarations, and that was a defining goal for where `record` types would be useful.
|
|
|
|
Another area worth examining is the original proposal for the `data` keyword. In the original proposal, primary constructors
|
|
|
|
would have meant the same thing in `record` types as well non-`record` types, and `data` would have been used as a modifier
|
|
|
|
on the primary constructor parameter to make it a public get/init property. `data` applied to a member, then, would have been
|
|
|
|
a natural extension and reuse of that keyword. With the original scenario gone, there are a few concrete scenarios we think
|
|
|
|
are highly related to, and will influence, the `data` keyword:
|
|
|
|
|
|
|
|
1. Use as a single-line in a discriminated union. While this is motivating, it's worth considering that, at least to some LDT
|
|
|
|
members, anything more than 2 properties as `data` members doesn't look great, and would perhaps work better as a multi-line
|
|
|
|
construct, which will line up visually. This seems a reasonable concern, so `data` may not be the solution we're looking for.
|
|
|
|
2. Use in required properties, as it seems likely that we will need some keyword to indicate that a property is a required
|
|
|
|
member in a type. While `data` could be orthogonal to some other required property keyword, it's likely there will be at least
|
|
|
|
some interaction as we'd likely want `data` to additionally imply required. It's also possible that we could have just a single
|
|
|
|
keyword to mark a property required, and it gives you what we believe are the useful defaults for such a property.
|
|
|
|
3. Use in general primary constructors as a way to say "give me the same thing a `record` would have for this parameter". This
|
|
|
|
would be somewhat resurrecting the original use case for the keyword, as we could then retcon `record` primary constructors
|
|
|
|
as implying `data` on all parameters for you, but you can then opt-in to them in regular primary constructors as well. `data`
|
|
|
|
members in a class, then, would again become a natural reuse and extension of the keyword on a primary constructor parameter.
|
|
|
|
|
|
|
|
#### Conclusion
|
|
|
|
|
|
|
|
We'll leave `data` out of the language for now. Our remaining motivating scenarios are things that will be worked on more in
|
|
|
|
C# 10 cycle, and absent clearer designs in those spaces the need and design factors for `data` are too abstract. Once we work
|
|
|
|
more on these 3 scenarios, we'll revisit `data` and see if a need for it has emerged.
|
|
|
|
|
|
|
|
### `ReadOnlySpan<char>` patterns
|
|
|
|
|
|
|
|
https://github.com/dotnet/csharplang/issues/1881
|
|
|
|
|
|
|
|
We have a community PR to implement the Any Time feature of allowing constant strings to be used to pattern match against
|
|
|
|
a `ReadOnlySpan<char>`. This would be acknowledging special behavior for `ReadOnlySpan<char>` with respect to constant strings,
|
|
|
|
but we already have acknowledged special behavior for `Span` and `ReadOnlySpan` in the language, around `foreach`. We also
|
|
|
|
considered whether this could be a breaking change, but we determined it could not be one: `ReadOnlySpan` cannot be converted
|
|
|
|
to `object` or to an interface as it is a ref struct, so if there exists a pattern today operating on one today the input type
|
|
|
|
of that pattern match must be `ReadOnlySpan<char>`. There were two open questions:
|
|
|
|
|
|
|
|
#### Should we allow `Span<char>`
|
|
|
|
|
|
|
|
The question is if `Span<char>` should also be allowed as well as `ReadOnlySpan<char>`. All of the same arguments about
|
|
|
|
compat apply to `Span<char>`. We also considered whether `Memory`/`ReadOnlyMemory` should be allowed inputs. Unlike `Span`/`ReadOnlySpan`,
|
|
|
|
though, there are backcompat concerns with `Memory` that cannot be overlooked, as they are not ref structs. It is very
|
|
|
|
easy to obtain a `Span`/`ReadOnlySpan` from a `Memory`/`ReadOnlyMemory`, however, so the need isn't as great.
|
|
|
|
|
|
|
|
##### Conclusion
|
|
|
|
|
|
|
|
`Span<char>` is allowed. `Memory<char>`/`ReadOnlyMemory<char>` are not.
|
|
|
|
|
|
|
|
#### Is this specific to `switch`, or can it be any pattern context
|
|
|
|
|
|
|
|
The original proposal here just mentioned `switch`. However, the implementation allows it to be used in any pattern context,
|
|
|
|
such as `is`.
|
|
|
|
|
|
|
|
##### Conclusion
|
|
|
|
|
|
|
|
Allowed in all pattern contexts.
|
|
|
|
|
|
|
|
|
|
|
|
#### Final Notes
|
|
|
|
|
|
|
|
We also discussed making sure that switching on a `Span`/`ReadOnlySpan` is as efficient for large switch statements as switching
|
|
|
|
on a string is. Over 6 cases, the compiler will take the hashcode of the string and use it to implement a jump table to reduce the
|
|
|
|
number of string comparisons necessary. This method is added to the `PrivateImplementationDetails` class in an assembly, and we
|
|
|
|
should make sure to do the same thing for `Span<char>` and `ReadOnlySpan<char>` here as well so the cost to using one isn't higher
|
|
|
|
than allocations would be from just doing a substring and matching with that.
|