# C# Language Design Meeting for April 6th, 2022

## Agenda

1. [Unresolved questions for static virtual members](#unresolved-questions-for-static-virtual-members)
2. [Parameter null checking](#parameter-null-checking)

## Quote of the Day

- "You need a cat to sit on your space bar while you hit a reaction button!"

## Discussion

### Unresolved questions for static virtual members

https://github.com/dotnet/csharplang/blob/main/proposals/static-abstracts-in-interfaces.md#unresolved-questions

#### Equality operators and conversions

Prior to this feature we didn't allow equality operators and conversions (implicit or explicit) in interfaces.

The main reason for not allowing equality operators in an interface is that you can't pair them with an overload of `Object.Equals` and `Object.GetHashCode`.

For conversions the reason is that there are already language-level conversions to and from interfaces, and we try to prevent user-defined conversions where the language already has them.

Why should we allow these as *virtual* static members when we don't allow them as *non-virtual* static members? Arguably the virtual declarations are very different: They do not really say that the interface *itself* has these operators and conversions, but rather that an implementing type would. You cannot call these operators and conversions on the interface itself, but only on a type parameter `T` that is *constrained* by the interface. So they don't really run afoul of the situations that motivate the restrictions on the non-virtual operators and conversions.

Static virtual equality operators and conversions are not an esoteric scenario - we eagerly use both in the new generic math interfaces, and usability would be diminished if we didn't allow them.

##### Conclusion

Stick to the plan. It's ok to have equality operators and conversions as static virtual members, even while interfaces don't allow them as non-virtual members.

#### Confirm placement of qualifier for explicit implementation

For explicit implementations of operators we currently put the `IMyInterface.` prefix before the `operator` keyword. Are we certain that this is the right place?

It is in fact one of the only places that consistently *exists* across all operator kinds, including conversions. Not all operators have an operator symbol for instance.

##### Conclusion

We like it where it is.

#### When does a type parameter count as "the type itself" in an operator declaration?

Up until now, operator declarations have required the containing type to occur as at least one of the operand types. In static virtual operators we relax that so that a type parameter gets to count as "the type itself", but we have been imprecise on what this means in the past. What should the exact requirement be?

In order for a type parameter `T` to count as "the enclosing type" the following are the strictest requirements we can express that still satisfy the scenarios:

- `T` is a direct type parameter on the interface in which the operator declaration occurs, and
- `T` is *directly* constrained by what the spec calls the "instance type" - i.e. the surrounding interface with its own type parameters used as type arguments.

Without self-types in the language we can't express a requirement that `T` is a self-type. On the other end we *could* probably choose to make the rules somewhat *loser* and still make lookup work.

##### Conclusion

We keep the strict rules outlined above. The rules on operators today are also quite strict, even though they could probably be loosened without breaking lookup, so this feels in line with the existing language. Furthermore we don't know of scenarios that would benefit from a looser rule, but if we find them we can always relax it later.

#### Query expressions over types

Since query expressions are spec'ed as a syntactic rewrite, C# actually lets you use a *type* as the query source, as long as it has static members for the query operators you use! In other words, if the *syntax* fits, we allow it!

Previously that could never apply to a type parameter, because you couldn't dot off of those, but now that we allow that, should we be true to the syntactic rewrite and allow a type parameter there?

##### Conclusion

We think this behavior was not intentional or important in the original LINQ, and we don't want to do the work to support it on type parameters. If there are scenarios out there we will hear about them, and can choose to embrace this later.

We don't believe there are other places in the language where this situation can occur, but will check.

#### Resolution rules for conversion operators

We need to adjust the operator resolution rules to look for operator declarations on type parameters constrained by interfaces that have static virtual operator declarations.

In the current implementation there is a stopgap rule that looks for operators in interface constraints essentially *only* when there is not also a class constraint. As soon as there is a class constraint, it hides the interface operators regardless of whether the class itself implements the operators.

More consistent with the behavior of member lookup would be that when there are no applicable candidates from the effective base class (essentially the class constraint) we just try again with the effective interfaces (essentially the interface constraints).

##### Conclusion

Proposal adopted.

### Parameter null checking

https://github.com/dotnet/csharplang/blob/main/proposals/param-nullchecking.md

This feature has caused some amount of debate in the community after being released in preview. After having talked to a number of constituencies our read is that there *is* a majority who are happy with the feature but others raise a number of objections at different levels. These are the opinions most commonly expressed:

1. There shouldn't be a feature; NRT is good enough and saving one line of code isn't worth it
2. We should go further and support other preconditions through more of a "contracts" feature
3. We should do it by allowing NRTs to incur runtime checking semantics, under a flag or otherwise
4. We should do it with the proposed semantics but with a different syntax

We do feel that this is a small syntactic-sugar feature that not everyone will be using - it is only for when you would have thrown `ArgumentNullException` in your code today. We think some of the fervor of the feedback may be driven by a perception that this is going to be more pervasive in code than it is.

Here is a brief summary of the reasons we have had for not taking one of the four paths outlined above; not to say that those aren't up for debate:

##### 1. Do nothing

We feel that the feature does have value. In most cases it only removes a line of code, but that could be the only line of code you had (e.g. in a constructor body that otherwise chains to another constructor), or the line that prevents you from using an expression body. In general, validation code can be voluminous enough to obscure the main logic, and the bulk of validation code by far is argument null checks.

Additionally the feature gives extra expressiveness in certain situations. In a constructor it will check a parameter *before* it is passed to a chained constructor. In an iterator it will be checked eagerly instead of during the first `MoveNext` call. In an async method it will throw synchronously instead of as part of the `await`.

In docs having a shorter syntax means we will include it in more examples (it will not obscure the main point of the sample) which means those examples will be "safer". That matters because many people copy from those samples.

So in summary the feature does add value.

##### 2. Do contracts

Over the years there have been dialects of C# that embraced generalized contracts to express and check preconditions (and sometimes postconditions) either statically, at runtime or both.

One important learning from these experiments is that around 9 out of 10 preconditions are null checking. Deal with that and the rest is in the noise. There would be very little extra value from the significant investment and likely syntax overhead of a generalized contracts feature of any kind.

In summary, null checks are the sweet spot for a feature.

##### 3. Use nullability annotations to generate checks

It was a core design pillar of nullable reference types (NRT) that they did not alter runtime semantics in any way. If your code compiles, it runs the same way no matter the annotations you have and the warnings you get. This tremendously simplifies the reasoning one has to do about the impact of the annotations.

Indiscriminately doing runtime checks based on nullability annotations would obviously be a massive breaking change, but we could of course put the behavior behind a flag - like we did NRT itself.

Generating runtime checks for every nonnullable parameter type may be overkill because you get checking in too many places, which is a performance hit, but also may not always be what you want. Of course that could be mitigated by an opt-out syntax on parameters, but now you're back to having just as much syntax, and sort of an arms race between opt-in and opt-out mechanisms.

##### 4. Use a different syntax

The `!!` syntax was arrived at after much debate. The feature started out with a single `!`, but it was felt that it was to easily confused with the `x!` null-forgiveness operator in expressions. Moreover we might want to apply null checking elsewhere in the future (e.g. on property setters/initers) and even maybe as an expression or statement. The latter would not be possible if we reused `!` for it.

Other proposed syntaxes were longer, diminishing the "savings" compared to just doing the check manually, though possibly improving readability. There is a tradeoff between what's the right "weight" of a feature when you're used to it, versus when you're first learning it. If we optimize for "self-explanatory" that may turn into annoyingly heavy-weight with frequent use.

##### Conclusion

We debated between the above overall approaches and concluded that we still have a clear lean towards the currently proposed semantics with *some* syntax. A few people were leaning in the direction of option 2 above, but the partial conclusion is that *if* we are going to do a parameter null checking feature it will be through *some* form of annotation of the parameter declaration, as originally proposed.

Running out of time we will save for later the decisions of:
- What *should* the syntax be for this feature, and
- Once we settle that, do we still feel confident that we want the feature at all.




