csharplang/meetings/2017/LDM-2017-03-08.md

151 lines
5 KiB
Markdown
Raw Permalink Normal View History

2017-04-15 00:52:56 +02:00
# C# Language Design Notes for Mar 8, 2017
2017-05-31 00:56:12 +02:00
## Agenda
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
We looked at default interface member implementations.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
1. Xamarin interop scenario
2. Proposal
3. Inheritance from interface to class
4. Overriding and base calls
5. The diamond problem
6. Binary compatibility
7. Other semantic challenges
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
# Xamarin interop scenario
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
Android interfaces are written in Java, and can therefore now have default implementations on members. Xamarin won't be able to seamlessly project those interfaces into .NET.
On iOS, Objective-C and Swift have protocols, of the general shape:
2017-04-15 00:52:56 +02:00
``` c#
protocol Foo
{
void Hello();
@optional
int Color { get; set; }
int Bye();
}
```
2017-05-31 00:56:12 +02:00
Again, the best we can do is to project the non-optional parts into C# interfaces, whereas the optional parts must be handled in less appetizing ways:
2017-04-15 00:52:56 +02:00
``` c#
interface IFoo
{
void Hello();
}
```
# Proposal
2017-05-31 00:56:12 +02:00
[The current proposal](https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md) is to allow the following in interfaces:
2017-04-15 00:52:56 +02:00
- member bodies
2017-05-31 00:56:12 +02:00
- nonvirtual static and private members
- overrides of members
Putting a concrete body on your method (etc), means you don't have to implement it on the class.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
Not proposing (but may want to look at):
- non-virtual public instance members
2017-04-15 00:52:56 +02:00
- protected and internal members
- nested types
- operator declarations
- etc...
Don't want:
- state!
- conversions
2017-05-31 00:56:12 +02:00
This gives parity with where Java is: they made tweaks over time based on feedback, and this is where they landed (private and static members were added).
2017-04-15 00:52:56 +02:00
The more you go to the side of adding things, the more this is a philosophy change for interfaces.
2017-05-31 00:56:12 +02:00
# Inheritance from interface to class
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
Important question: is the member inherited into the class, or is it "explicitly implemented"? Our assumption is that it's explicitly implemented:
2017-04-15 00:52:56 +02:00
``` c#
2017-05-31 00:56:12 +02:00
interface I { int M() => 0; }
class C : I { } // C does not have a public member "M"
2017-04-15 00:52:56 +02:00
```
2017-05-31 00:56:12 +02:00
That means you can't "just" refactor similar implementations into an interface the way you do into a base class.
# Overriding and base calls
Should overrides be able to call `base`? Yes, but we would need new syntax to deal with ambiguity arising from the fact that you can have more than one base interface:
2017-04-15 00:52:56 +02:00
``` c#
2017-05-31 00:56:12 +02:00
interface I1 { void M() { WriteLine("1"); } }
interface I2 : I1 { override void M() { WriteLine("2"); } }
interface I3 : I1 { override void M() { WriteLine("3"); } }
interface I4 : I2, I3 { override void M() { base(I2).M(); } }
2017-04-15 00:52:56 +02:00
```
2017-05-31 00:56:12 +02:00
The exact syntax for disambiguating base calls is TBD. Some ideas:
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
``` c#
I.base.M() // what Java has
base(I).M() // similar to default(I)
base.I.M() // ambiguous with existing meaning
base<I>.M() // looks like generics
```
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
# The diamond problem
`I4` above inherits two default implementations, one each from `I2` and `I3`, but explicitly provides a new implementation, so that there's never a doubt as to which one applies. However, imagine:
2017-04-15 00:52:56 +02:00
``` c#
2017-05-31 00:56:12 +02:00
interface I5: I2, I3 { }
class C : I2, I 3 { }
2017-04-15 00:52:56 +02:00
```
2017-05-31 00:56:12 +02:00
The class declaration of `C` above must certainly be an error, since there is no good (symmetric) unambiguous way of determining a default implementation. Also, the interface declaration of `I5` should be an error, or at least a warning.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
This means that adding an override to an interface can break existing code. This can happen in source, but depending on compilation order, it may also be possible to successfully compile such code.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
# Binary compatibility
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
What should the runtime do when an interface adds a default implementation and that causes an ambiguity in code that isn't recompiled?
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
Should this be a runtime error? It would lead to some hard-to-understand failures. Should it "just pick one"?
"Why did you not let my program run?" vs "why did you not prevent this hole"?
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
We need to decide what is less harmful. We should look at what Java does and why.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
# Other semantic challenges
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
The feature reveals some "seams" between C# and the CLR in how they understand interface overriding. In the following, imagine `I`, `C` and the consuming code are in three different assemblies:
2017-04-15 00:52:56 +02:00
``` c#
// step 1
2017-05-31 00:56:12 +02:00
interface I { }
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
class C : I { public void M() { /* C.M */ } }
2017-04-15 00:52:56 +02:00
// step 2
2017-05-31 00:56:12 +02:00
interface I { void M() { /* I.M */} }
2017-04-15 00:52:56 +02:00
I i = new C();
2017-05-31 00:56:12 +02:00
i.M(); // calls I.M default implementation
2017-04-15 00:52:56 +02:00
// step 3: recompile C
I i = new C();
2017-05-31 00:56:12 +02:00
i.M(); // calls C.M non-virtual method
2017-04-15 00:52:56 +02:00
```
2017-05-31 00:56:12 +02:00
The problem here is that the runtime doesn't consider non-virtual members able to implement interface members, but C# does. To bridge the gap, C# generates a hidden virtual stub method to implement the interface member. However, during step 1 there is no interface member to implement, and during step 2 the class declaration isn't recompiled. The runtime doesn't consider `C.M` an implementation of `I.M`, so if you call `M` on a `C` through the `I` interface, you get the default behavior. As soon as `C` is recompiled, however, the compiler inserts its stub, and the behavior changes.
2017-04-15 00:52:56 +02:00
2017-05-31 00:56:12 +02:00
We have to decide it there is something we can and will do about this. We may just accept it as a wart.