csharplang/meetings/2021/LDM-2021-06-07.md
2021-07-27 12:34:38 -07:00

5.4 KiB

C# Language Design Meeting for June 7th, 2021

Agenda

  1. Runtime checks for parameterless struct constructors
  2. List patterns
    1. Exhaustiveness
    2. 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:

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:

_ = 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:

_ = 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.