csharplang/meetings/2017/CLR-2017-03-23.md

4.7 KiB

2017-03-23 CLR Behavior for Default Interface Methods

Met today with

To discuss the CLR handling of default interface methods.

(1) Runtime handling of ambiguities

Question (1) is about what the CLR should do when the runtime does not find a most specific override for an interface method. For example, if this program is compiled (each interface being in a separate assembly)

public interface IA
{
    void M();
}
public interface IB : IA
{
    override void IA.M() { WriteLine("IB"); }
}
public interface IC : IA
{
}
class C : IB, IC
{
    static void Main()
    {
        IA a = new C();
        a.M();
    }
}

and then subsequently IC is changed

public interface IC : IA
{
    override void IA.M() { WriteLine("IB"); }
}

This would cause an error at compile-time if class C were recompiled (because there is no most specific override for IA.M). However, what happens if this program is run without recompiling C?

There are two most choices:

  1. The runtime selects, deterministically, between the implementations of IA.M in IB and IC; or
  2. The runtime fails at some time before this invocation is run. a. At load time; or b. At the time of the invocation

We did not have consensus which approach is preferred; there are advantages and disadvantages to either. We will defer this issue until it can be discussed in a larger group.

Static methods

Static methods are already permitted in interfaces.

Protection levels

From the point of view of the CLR, once we start accepting a protection level other than private, we might as well accept all of them. The semantics are clear.

With the possible exception of protected and its related protection levels. We would need to clearly define both the language and runtime constraints implied by this protection level. For example, is a protected member in an interface accessible in a class that implements the interface? What would be the syntax in source (note that interface members are not inherited into classes that implement them)? What would be the verifiable form of the generated IL?

We tentatively agreed that the CLR will accept protection levels other than public in interfaces, but open questions remain about protected.

Sealed override

It is an open question whether a sealed override should be permitted within an interface. Given the most specific override rule, such code could prevent unrelated sister interfaces from implementing the method.

If it were permitted, we need to check that there is a way to represent it in IL.

Non-virtual methods in interfaces

Non-virtual methods in interfaces are not a problem for the CLR, presuming we have a way to represent them. We need to check that existing interface methods have a newslot bit set. We think so. If so, then we would simply omit that from non-virtual methods.

Implicit overrides in interfaces

We agree that the compiler-generated IL for an "implicit" override

public interface IA
{
    void M();
}
public interface IB : IA
{
    override void M() { WriteLine("IB"); }
}

should be roughly the same as for an "explicit" override

public interface IB : IA
{
    override void IA.M() { WriteLine("IB"); }
}

In both cases the compiler produces a methodimpl record for the overridden method(s). The CLR will not consider a method in an interface to be an implementation of another method without the methodimpl bit. We should confirm that we are OK with the binary compatibility implications of this.

All override declarations should omit the newslot bit to ensure no new vtable slot is allocated.

Open Question: Which interfaces are searched

There is an open question which interfaces are searched for implementations, and in which order. For example, if we have

interface IA { void M() {} }
interface IB: IA { override void M() {} }

class Base : IB { }
class Derived : Base, IA { }

Then is the override appearing in IB the final override selected for class Derived, even though IB does not appear in the interface list for Derived?

Open Question: Diamond Inheritance between Class and Interface

What should occur in cases involving diamond inheritance with classes and interfaces:

interface IA { void M() {} }
interface IB: IA { override void M() {} }

class Base : IA { void IA.M() { } }
class Derived : Base, IB { }

In this case neither Base nor IB is a subtype of the other. Is class Derived legal? If so, what is its implementation of IA.M?