# C# Language Design Notes for Nov 14, 2018 ## Agenda 1. Base call syntax for default interface implementations 2. Switch exhaustiveness and null ## Discussion ### Default interface implementations Example of base calls: ```C# interface I1 { void M(); } interface I2 { void M(); } interface I3 : I1, I2 { void I1.M() { } void I2.M() { } } interface I4 : I1, I2 { void I1.M() { } void I2.M() { } } interface I5 : I3, I4 { void I1.M() { base(I1).M(); base(I1).M(); } void I2.M() { base(I2).M(); base(I2).M(); } } ``` Q: Should we even allow the programmer to call explicit implementations? In existing C# code this is not allowed since all explicit implementations are private. You can always call by casting to the interface if the interface is not re-implemented, but there's no way to do this via a base call. However, there's no way to use overriding default interface implementation in C# 8.0 *except* for explicit implementation, so not allowing it would basically not allow the scenario. Java does allow this scenario, so full compat would require us to add the feature. Q: What type of dispatch would we use in the runtime? One way is to constrain to the interface, then do a virtual dispatch for a particular method. This probably would require additional (fairly expensive) runtime work. Q: What does the syntax need to express? One problem is for `I3`, where by signature `M` is ambiguous because it's not clear whether you mean `I1.M` and `I2.M`. The syntax would need to specify both that the `I3` implementation is requested *and* whether or not to choose `I1.M` or `I2.M`. One other option is to just not provide a way of disambiguating `M`. If there's an ambiguity you can't call any particular `M`. **Conclusion** Let's do the simplified form. If the `M` being called is ambiguous, it's illegal to call such a method. The only configuration we're considering is which base to look for the method. #### Syntax ```C# namespace N { interface I1 { int M(string s) => s.Length; } } class C : I1 { int I1.M(string s) { // Options: base>.M(s); base(N.I1).M(s); N.I1.base.M(2); base{N.I1} base:N.I1.M(s); base!N.I1.M(s); base@N.I1.M(s); base::global::N.I1.M(s); base.(N.I1).M(s); (base as N.I1).M(s); // base isn't an expression, so this isn't legal today ((N.I1)base).M(s); // same here base.N.I1.M(s); // clash with a member on base, doesn't allow for qualified name N.I1.M(s); // this is currently a static call on I1, can't work } } ``` We think the forms that take advantage of `base` not being an expression are too clever and would just be confusing. `N.I1.base.M(2)` is possibly tricky for human readability since `base` is in the middle and is harder to notice without coloration. One problem with `base(N.I1).M(s)` is if we want to add "invocation" operators to the language, that would disallow invoking your base. `base::N.I1.M(s)` has some history in the language. **Conclusion** Decided on `base(N.I1).M(s)`, conceding that if we have an invocation binding there may be problem here later on. ### Switch exhaustiveness and null Current design: warning about not handling all inputs *except* null. Q: What happens when the switch actually doesn't match what was given at compile time? **Proposal**: A new `MatchFailureException` that extends `ArgumentException`. This allows the compiler to use an existing exception for down-level support. There's no data, just a message. **Conclusion** The base should be `InvalidOperationException`, not `ArgumentException`. If the argument being switched on can be trivially boxed into object, we'll add it as an argument to the `MatchFailureException`. Otherwise, just fill in `null`.