csharplang/meetings/2017/LDM-2017-04-19.md
2017-06-01 17:50:56 -07:00

3 KiB

C# Language Design Notes for Apr 19, 2017

Quote of the day: "I'm not thrilled with the decision, but it was a constrained call"

Agenda

  1. Improved best common type
  2. Diamonds with classes
  3. Structs and default implementations
  4. Base invocation

Improved best common type

b ? null : 7 // should be int? not error

This is a small, isolated change that leads to a nice improvement. Let's do it!

Diamonds with classes

A class implementation of an interface member should always win over a default implementation in an interface, even if it is inherited from a base class. Default implementations are always a fallback only for when the class does not have any implementation of the member at all.

Structs and default implementations

It is very hard to use default implementations from structs without boxing. In general, the only way to use interface methods on a struct without copying or boxing is through generics and ref parameters:

public static void Increment<T>(ref T t) where T : I => t.Increment();

But for default implementations, even that won't necessarily avoid boxing! What is the type of this in the default implementation of I.Increment? If it's I, then this is a boxed form of the struct! We can avoid that by having an implicit type parameter, a "this type", which is constrained by I. But then, what is the name of that implicit type parameter in the body? If it doesn't have a name, you can't use it in the body. If it does have a name, what is the syntax for that?

For inherited members on structs today, they do in fact get boxed, but it is never observable - because the behavior of those three methods (ToString, etc.) doesn't mutate, or otherwise reveal the boxing.

What can we do?

  1. Forbid structs to make use of default implementations: Can't do that. Now adding a new interface member with a default implementation would break implementing structs.
  2. Come up with some code gen strategy like implicit this types: Pushes the problem elsewhere, at a high cost and with a high risk that you box later anyway.
  3. Don't worry about it, and leave it as a wart in the language: It looks like a cop out, but there really doesn't seem to be a good alternative. C# already has places where structs and interfaces behave weirdly together; this just adds to the pile.

Conclusion

Let's stick with option 3. It would be a breaking change to do 2 later, so we can never change it.

Base invocation

What should be the syntax for base invocation? Some candidates:

I.base.M()
base(I).M()
base<I>.M()

base(I) is the winner. It reads like default(T), and seems the best fit.

Should these be permitted in classes too, to specify base interface implementations? Yes. Otherwise the diamond problem isn't solved.

We could actually expand it to work on classes too, to allow a more distant base implementation to be called. Let's decide that later.

In an interface, can I say base(IA) that isn't a direct base interface? Yes.

In a class, can I say base(IA) that isn't a direct base interface? Yes.