117 lines
5.4 KiB
Markdown
117 lines
5.4 KiB
Markdown
# C# Language Design Meeting for June 7th, 2021
|
|
|
|
## Agenda
|
|
|
|
1. [Runtime checks for parameterless struct constructors](#runtime-checks-for-parameterless-struct-constructors)
|
|
2. [List patterns](#list-patterns)
|
|
1. [Exhaustiveness](#exhaustiveness)
|
|
2. [Length pattern feedback](#length-pattern-feedback)
|
|
|
|
## Quote of the Day
|
|
|
|
- "They should probably be told off, I was going to say something else, but they should be told off in code review"
|
|
|
|
## Discussion
|
|
|
|
### Runtime checks for parameterless struct constructors
|
|
|
|
https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/parameterless-struct-constructors.md
|
|
|
|
In testing parameterless struct constructors, we've found a new bug with `Activator.CreateInstance()` on older frameworks. This code
|
|
will print `True, False` on .NET Framework 4.8:
|
|
|
|
```cs
|
|
System.Console.WriteLine(CreateStruct<S>().Initialized); // True
|
|
System.Console.WriteLine(CreateStruct<S>().Initialized); // False (On .NET Framework)
|
|
|
|
T CreateStruct<T>() where T : struct
|
|
{
|
|
return new T();
|
|
}
|
|
|
|
struct S
|
|
{
|
|
public readonly bool Initialized;
|
|
public S() { Initialized = true; }
|
|
}
|
|
```
|
|
|
|
This is due to a caching bug in the framework, at it essentially means that `Activator.CreateInstance()` will only correctly invoke a
|
|
struct constructor the first time it is used, and any subsequent calls in the same process will run the constructor and then zero-init
|
|
on top of that value, so side-effects (such as `Console.WriteLine`) would be observed, but field initialization would not. This is similar
|
|
to the bug that sunk parameterless struct constructors the last time we attempted to add them, but .NET Core and 5 do have the correct
|
|
behavior here. As .NET Framework is not a supported target platform for C# 10, however, we don't think this is a showstopper like it would
|
|
have been back in C# 6. We think that this is a good place for an analyzer to warn consumers, as the compiler itself doesn't want to try
|
|
and infer what framework a user is targetting based on the presence of APIs, and runtime feature checks in the compiler are always hard
|
|
errors and cannot be worked around.
|
|
|
|
#### Conclusion
|
|
|
|
An analyzer will be incorporated into the default analyzer pack to warn users about use of parameterless struct constructors on older,
|
|
unsupported framework targets.
|
|
|
|
### List patterns
|
|
|
|
https://github.com/dotnet/csharplang/issues/3435
|
|
|
|
#### Exhaustiveness
|
|
|
|
We have a couple of test examples that add some exhaustiveness complications:
|
|
|
|
```cs
|
|
_ = list switch
|
|
{
|
|
{ .., >= 0 } => 1,
|
|
{ < 0 } => 2,
|
|
{ Count: <= 0 or > 1 } => 3,
|
|
};
|
|
```
|
|
|
|
This switch expression should be considered exhaustive, but it will require understanding that the `>= 0` in the first pattern _can_ apply
|
|
to element 0, and that the last expression should then handle all the rest of the cases. Another similar case is this one:
|
|
|
|
```cs
|
|
_ = list switch
|
|
{
|
|
{ .., >= 0 } => 1,
|
|
{ ..{ .., < 0 } } => 3,
|
|
};
|
|
```
|
|
|
|
While this example is a bit silly, it illustrates the general thing we want to: list patterns on a slice should count towards the containing
|
|
list pattern for exhaustiveness. While there is the possibility that this means a slice could give bad results (ask for a slice from x to y,
|
|
get the wrong thing back), we generally consider such types to be intentionally subverting user expectations. A list pattern would not be the
|
|
only place where such a type mislead a user, and we don't think there's a reasonable way to protect against such types.
|
|
|
|
##### Conclusion
|
|
|
|
We should make exhaustiveness work for these scenarios.
|
|
|
|
#### Length patterns
|
|
|
|
We've heard from a number of our more heavily-invested users through channels such as Twitter, Discord, Gitter, and GitHub Discussions that
|
|
our existing plan for length patterns have been generally confusing and/or actively misleading. The major issues that have been raised:
|
|
|
|
1. While there is symmetry with array size specifiers in construction, that symmetry doesn't carry to any other collection creation.
|
|
2. The `[]` syntax is most often used for accessing at an index, which length patterns don't do.
|
|
3. It is extremely tempting to do `{}` as the empty list pattern as a reduction of the rest of the patterns.
|
|
|
|
We've brainstormed a few ways to try and address various parts of this feedback:
|
|
|
|
1. Have a special `length` pattern that can be used in a list pattern: `{ 1, 2, 3, length: subpattern }`. This pattern can only be used in
|
|
list patterns, so the empty list would be `{ length: 0 }`. This can help with issues 1 and 2, but not with 3.
|
|
2. Go back to square brackets, as in the original proposal. This helps with all 3 issues, but reintroduces the new issue that `[]` isn't
|
|
symmetric with array and collection initializers.
|
|
3. Use parens/positional patterns. This seems interesting, but has a problem because positional patterns will check for `ITuple` on inputs
|
|
today.
|
|
4. Require using indexers in nested patterns: `{ [0]: pattern, [2]: pattern }`. This is understandable, but extremely verbose.
|
|
5. A more general version of 1: just allow combining list and property patterns into one `{}`. We could potentially have a separator token
|
|
such as `,` or `;`, which would allow us to have a pretty simple empty list pattern of `{ , }` or `{ ; }`.
|
|
|
|
We didn't get too deep on any particular syntax with the time remaining, but we are convinced that we need to take another look at these.
|
|
|
|
##### Conclusion
|
|
|
|
A smaller group will hammer out a proposal and bring it back to LDM. We need to take the time to get this right, which may mean that the feature
|
|
slips and does not make C# 10.
|