5.4 KiB
C# Language Design Meeting for June 7th, 2021
Agenda
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
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 targeting 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:
- While there is symmetry with array size specifiers in construction, that symmetry doesn't carry to any other collection creation.
- The
[]
syntax is most often used for accessing at an index, which length patterns don't do. - 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:
- 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. - 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. - Use parens/positional patterns. This seems interesting, but has a problem because positional patterns will check for
ITuple
on inputs today. - Require using indexers in nested patterns:
{ [0]: pattern, [2]: pattern }
. This is understandable, but extremely verbose. - 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.