csharplang/meetings/2017/LDM-2017-03-21.md
2018-01-25 13:11:49 -08:00

5.6 KiB

C# Language Design Notes for Mar 21, 2017

Agenda

Discussion of default interface member implementations, based on this guided tour.

  1. Concerns raised on GitHub and elsewhere
  2. Inheritance?
  3. Breaking name lookup on this
  4. Events
  5. Modifiers
  6. Methods
  7. Properties
  8. Overrides
  9. Reabstraction
  10. Most specific override
  11. Static non-virtual members
  12. Accessibility levels
  13. Existing programs

Concerns raised in various fora

  • Traits: Not what we're out to address! It may have similar capabilities, but the design point is different, based on different motivations.
  • We are also not out to add multiple inheritance to the language - we already have it with interfaces, and we're just making sure that continues to work.
  • It's a bit funny that members of interfaces are inherited into interfaces but not into classes.

Interesting design point: This isn't really fully traits, because interface members aren't inherited. The interfaces can't be building blocks for classes.

For structs, there aren't really any other ways to factor out behavior.

Java did this years ago, and the world did not fall apart.

Inheritance?

We could consider letting default-implemented interface members be inherited into classes. That would be a big break away from interfaces today, where an implementing class always has to do something to get a corresponding public member. It would also probably make it a breaking change to add a default implementation to an existing member, which does not seem attractive.

We could have a feature where when you specify a base interface you can ask to have its default implemented members inherited.

Breaking name lookup on this

There's a new kind of breaking change that may arise from adding new members to interfaces, where derived interfaces will get name lookup conflicts in their concrete implementations:

itf IA { void Foo() {} }

itf IB {}

itf IC : IA, IB 
{ 
    void Bar() 
    { 
        Foo();            // allowed? What if IB adds Foo()?
        this.Foo();       // same? different?
        ((IA)this).Foo(); // would be allowed
        this(IA).Foo();   // new syntax?
    } 
}

Events?

Absolutely. They aren't materially different. Any member you can declare in an interface should be able to have default implementations.

Modifiers

Virtualness is default in interfaces today. We could allow sealed to mean non-virtual if you want to override that.

Public is default in interface. Should we allow the default to be explicitly stated? All other accessibilities are potentially interesting as well.

extern scenario: people really like the idea of a scoped PInvoke. People want it for local functions as well.

static? Yes

Nested types?

Philosophically, we can either go all (or most of) the way to what classes can do, or do it as sparingly as possible, adding only things that are really needed. Once I can put implementation in an interface, I want all of the other stuff. Moving a method implementation from a class up to an interface shouldn't all of a sudden put a bunch of limitations on how to implement it.

Move from "let me have this" to "tell me why I can't have this".

Methods

Can I use plain old base (without disambiguation)?

  • Is it of type object, and exposes object methods?
  • disallow?
  • base means "the" base interface, if applicable - may even "mesh" the base interfaces.

No base for now, just for initial simplicity.

Properties

Abstract properties and events let you implement just one accessor. Can interface properties do that? Our strawman is "yes".

Overrides

  • Allow explicitly overriding an interface member that you override?
  • Allow implicitly overriding ...?
interface I1 { void M(); }
interface I2 : I1 { override void I1.M() { ... } } // "explicit" override
interface I3 : I1 { override void M() { ... } }    // "implicit" override

Allowing the latter means that the override keyword is needed, in order to avoid syntactic ambiguity. The latter may override all such members? Yes. Direct and indirect.

There's a bit here about which "class" analogy we apply: the "class inherits class" analogy, or the "class implements interface" analogy. The latter doesn't really apply anyway, so it's more similar to "class inherits class". Therefore we should require the override keyword even on IA.M explicit member overriding.

Properties, can override just one accessor.

Tuple names must match, just as between classes. For dynamic, same as between classes.

reabstraction

Allow an override to remove implementation. abstract keyword is optional.

Most specific override

There has to be a most specific override, otherwise it's an error.

Properties and events, the accessors could in principle be treated independently (though there may be a problem with the Roslyn API). Let's instead say that it happens at the whole property level: there must be a most specific override of the property itself.

static non-virtual members on interfaces

Would be useful even without this feature. But a shared helper may also be static.

all accessibility levels?

public and private for sure. Let's investigate what internal and protected mean.

Existing programs

We think that example 3 (in this section of the guided tour) is analogous to 2, and should be allowed. However, how can we help someone who thought they were implementing the interface member, but were writing an unrelated private method?

Deciding whether a method implicitly implements an interface method is not just matching by signature.

We should accept the behavior now, and then look at pitfalls here later.