- "And we're almost there (famous last words, I'll knock on something)"
- "What about
record delegate
... doesrecord interface
make sense... I'll go away now"
First, we looked at what syntax we would use to specify struct
types that are records. There are two possibilities:
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
.
By unanimous agreement, record struct
is the preferred syntax, and record class
is allowed. record class
is
synonymous with just record
.
With data
, we come back to the same question we had at the end of Monday: 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:
- 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, sodata
may not be the solution we're looking for. - 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 wantdata
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. - 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 retconrecord
primary constructors as implyingdata
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.
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.
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:
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.
Span<char>
is allowed. Memory<char>
/ReadOnlyMemory<char>
are not.
The original proposal here just mentioned switch
. However, the implementation allows it to be used in any pattern context,
such as is
.
Allowed in all pattern contexts.
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.