131 lines
No EOL
3.8 KiB
Markdown
131 lines
No EOL
3.8 KiB
Markdown
|
|
# 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<I3>(I1).M();
|
|
base<I4>(I1).M();
|
|
}
|
|
void I2.M()
|
|
{
|
|
base<I3>(I2).M();
|
|
base<I4>(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<T> { int M(string s) => s.Length; }
|
|
}
|
|
class C : I1<T>
|
|
{
|
|
int I1.M(string s)
|
|
{
|
|
// Options:
|
|
base<N.I1<T>>.M(s);
|
|
base(N.I1<T>).M(s);
|
|
N.I1<T>.base.M(2);
|
|
base{N.I1<T>}
|
|
base:N.I1<T>.M(s);
|
|
base!N.I1<T>.M(s);
|
|
base@N.I1<T>.M(s);
|
|
base::global::N.I1<T>.M(s);
|
|
base.(N.I1<T>).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<T>.M(s)` has some history in the language.
|
|
|
|
**Conclusion**
|
|
|
|
Decided on `base(N.I1<T>).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`. |