- "I think we're just vague about where we become soup"
We're following up to last week's discussion on parameter null checking, deciding on our answers to two questions:
- Should we change the syntax of the feature, and if so, what syntax should we choose?
- Do we still feel confident in this feature at all?
To think about the first question, we put up some syntax ideas. Some of these have been previously discussed, others arose from community discussion on this feature. We also went through and listed some pros and cons for each syntax form.
void M(string s!!);- The current form- This syntax is perceived as very shouty, which we can understand. It does generalize to an expression form later though, which is nice to think about the future.
void M(string! s);- We dislike putting the modifier on the type, as it implies that
!can be put on types in other locations, and we don't have any idea on how we'd actually enable such a feature in the future. It also negatively affects usage in lambdas, as users would be required to add a type to their lambda declarations to use the feature. - We also considered whether
Type!could be used on a local, which would force a null check whenever something is assigned to that local. There was not much support this extension after consideration, however.
- We dislike putting the modifier on the type, as it implies that
void M(string s!);- This is much less shouty, as it's only one
!character. However, we're concerned about confusing all the different meanings of!; for example, the!'s in this example mean different things:x! => x!;.
- This is much less shouty, as it's only one
void M(notnull string s);- A keyword can be more clear, but we're not certain this is. It feels redundant, as the parameter is already declared as not null. It also has a different meaning than
the
NotNullattribute.
- A keyword can be more clear, but we're not certain this is. It feels redundant, as the parameter is already declared as not null. It also has a different meaning than
the
void M(string s ?? throw);- On the one hand, this feels pretty intuitive, as
?? throwis already legal expression syntax (provided there's an exception type on the other side). However, we're concerned with the interaction with default parameters, asstring s ?? throw = ""looks very confusing, andstring s = "" ?? throwlooks like?? throwis part of the default parameter value.
- On the one hand, this feels pretty intuitive, as
void M(string s is not null);- Our concerns here are the same as 5.
void M(checked string s);- We like that
checkedalready deals with exceptions, and it feels like the right word to use: when used with arithmetic, it ensures that users aren't violating the bounds of a type, which is very similar to parameter null checking. However,checkedcan already be applied to expressions, or even turned on project-wide, and we're not sure how we'd apply the null-checking feature in these locations: should all parameters be null-checked if the project-wide setting is on, for example? Or should a!in a checked block be checked for null?
- We like that
void M(string s) where s is not null;- This looks like a promising start for a full contracts feature, but not for a feature only doing null checking of parameters.
After the discussion leading to the above points, we felt that only two of the options had enough support to continue discussion: option 1, the current form, and option 3, the less shouty version of the current form.
We then turned our focus to question 2: do we still feel confident in this feature at all? As part of this, we also looked at the initial feedback we received on this feature by a number of channels: GitHub, social media, MVPs, conference audiences, internal reviewers, and the LDT itself. We do see a majority of positive reactions to the feature, but it isn't the large majority we normally like to see for C# features, particularly ones that are as broadly applicable as this feature will be, and even the LDT is split on the feature itself. While we think the preview provided us with valuable feedback and was worth pursuing, we ultimately decided that, between options 1, 3, or pulling the feature from C# 11 entirely, pulling the feature is the right move. We may revisit at a later date, if we ever want to pursue more generalized contracts, but for C# 11 this feature will removed.
Parameter null checking is removed from C# 11.
This next change in design of this feature pares it back significantly, basically back to the original design, but with a couple of key differences:
- There is no
privatekeyword here, which was a significant stumbling block in understanding of the feature and unintended feature creep. - There are more heavy restrictions on how the feature can be used.
In particular, this version solves the immediate source-generator issues that the BCL is running into, while allowing us design room if we ever want to expand the
feature in the future. We do need to validate that the restrictions around partial types and signatures will solve all the issues the BCL source-generators have, but
we think it's a forward direction we can make progress on.
The updated proposal can move forward, and we should validate the restrictions on partial types will solve the scenarios in the BCL.