meetings/2021/LDM-2021-06-14.md
https://github.com/dotnet/roslyn/issues/52745#issuecomment-849961999
https://github.com/dotnet/csharplang/issues/287
Conclusion: yes, we should support VB here.
This question centers around this example:
void M([CallerMemberName]string arg1 = "1", [CallerArgumentExpression("arg1")]string arg2 = "2")
{
Console.WriteLine(arg2); // What gets printed?
}
void M2()
{
M();
}
As we see it, there are 5 possible values that a reasonable programmer could expect.
null"".arg1, as an expression: "\"1\"".arg2: "2".arg1, as an expression: "\"M2\"".We don't think option 1 is useful here, as the parameter is attributed to not accept null, and this
would just mean that every use of CallerArgumentExpression would be required to handle the null case.
We also don't think that options 3 or 5 are really correct either: the attribute here is about providing
the specific syntax the user used, not the value the user used. There are many ways to express the values
given as a constant value: we could just turn "M2" into a string, or we could say "\"" + "M" + "2" + "\"".
Both are technically correct, but neither reflects what the user actually wrote. Finally, for option 3, we
think that this is trying to second-guess the user. They provided a default value for the parameter, and if
we never respect that value then the default value was useless. Given these, we think the correct approach
is option 4.
Option 4: the default value of the parameter will be used. We will not turn compiler-generated code into equivalent C# expressions.
Consider these examples:
void M3([CallerArgumentExpression("arg1")]string arg1 = ""); // Warning?
M3(); // What gets passed? null? ""?
We think this is absolutely worth a warning in source code, and if in metadata then we should just provide the default value of the parameter.
Consider this example:
M(arg1: /* Before */ "A" + /* Mid */ "B"
/* After */); // What is passed for arg2?
There are 3 possible answers for this:
arg1: to the end of the position, either ) or ,,
depending on whether the argument is followed by another or not.arg1: to the end of the position, not including the
argument specifier."A") to the end of the real executable C# code (the end of "B").While there are legitimate argument for 1 or 2, we don't think they provide enough benefit to make up for the fact that they will be including leading and trailing whitespace that we don't believe is useful for the users of this attribute. Given this, we think option 3 is the correct way forward.
Option 3: we go from the start of real C# executable code to the end of the expression, not including any leading or trailing trivia.
https://github.com/dotnet/csharplang/issues/3435
We've heard a lot of community feedback around our existing proposal for length patterns, which looks like this:
_ = list is [0]; // List has length 0;
Top among user feedback is that this syntax is:
{ } is not the empty list case, despite being what otherwise appears to be an empty list pattern.
While in some cases this happens to work because all that's left to handle is when the input is non-null, we don't
think it will lead to clear code.A smaller group met to try and brainstorm some approaches to solving the issue. These are:
[]) to denote a list pattern. This breaks with
the correspondence principle, but it does have stronger parallel with other languages, has a natural base case, and
we could potentially add a new creation form that achieves correspondence (and take the time to address things like
ImmutableArray<T>, which cannot be initialized by collection initializers today).;: { 1, 2, 3; } or { ; }. This separator would be
required, giving a few advantages:
These suggestions led to spirited debate. An unfortunate truth here is that, no matter what approach we take, we have
discrepency with some aspect of the language. The semicolon separator approach allows us to mostly keep in line with
collection initializers, but the trailing ; being required is very different and a wart. Square brackets, on the other
hand, are very different from the rest of C#. Today, square brackets are used for indexing operations and for
specifying the length of an array. Nothing in C# uses them to denote a group of things that is a collection. There are
proposals to use these brackets for an improved version of collection initializers though, giving us an opportunity for
future fulfillment of the correspondence principle, even if it won't be fulfilled on initial release. Patterns also
already have some discrepency with the rest of C#, particularly around and/or/not patterns, which aren't words used
in the rest of the language.
We will go with option 1: using square brackets for the list pattern. We still need to decide if and how these can be combined with recursive patterns, but it gives us the most flexibility with regard to future regularity in the language.
Orthogonally, we have also come up with a few suggestions for the length pattern:
length keyword as a property pattern: { length: 10 }. When a type is Countable, this
property is available, and it will bind to Length or Count as appropriate.Length and Count properties on:
Given that we've chosen square brackets for our new list pattern syntax, option 3 is out. This leaves us with option
1 or 2. We originally wanted special length patterns in the language because we wanted list patterns to work on a type
that didn't have a Length or Count property: IEnumerable. While we still want to do this, the implementation work
is quite complex and we think that it might not get into the initial version. So, while we're not ruling out 1, we don't
think it's necessary quite yet.
Option 2 is nice, but it has a couple of wrinkles. First, it's a breaking change, because we specially recognize that
the property in question cannot be negative. This can affect flow analysis and introduce warnings or errors about
unreachable patterns, and remove warnings about non-exhaustive switch expressions. It's not pretty, but we think we can
tie this recognition to a warning wave. It will be the first time a warning wave removes warnings, instead of adding
them, but we think it's the right move. Second, what types should we specially recognize here. Countable is a very broad
definition in C#: it pretty much just means has an accessible property named either Count or Length. We think that's
too broad for general recognition; while collections should never have negative lengths, the word Count or Length on
its own is not strong enough evidence that the type is a collection. Instead, we think we should require both countable
and indexable, the same requirements for using a list pattern in the first place. This will ensure that the type at least
behaves like a collection, and while there still might be such types that return negative Counts or Lengths, patterns
are only one place where such types will confuse their users and we don't think it's an edge case that should derail the
whole feature.
We will specially recognize the Count and Length properties on types that are both countable and indexable, assuming
that it can never be negative.
Given that the changes we've made today are specifically driven by community feedback, we feel that this feature needs more bake time than is left in the C# 10 cycle. The feature will ship in preview, either with C# 10 (like static abstracts in interfaces) or shortly after 10 is released. We want to make sure that the course-corrections we're making here help community understanding of the feature, and we don't have enough time before C# 10 is released to implement the changes and get them in customer hands before 10 is declared final.