csharplang/meetings/2019/LDM-2019-02-25.md
2019-03-11 00:42:53 -07:00

2.3 KiB

C# Language Design Notes for Feb 25th, 2019

Agenda

Semantics of base() calls in default interface implementations and classes

Discussion

The exact semantics of base() are still unresolved. Consider the following legacy base call.

class A 
{
    virtual void M() {}
}

class B : A 
{ 
    // override void M() { }
}

class C : B
{
    override void M() => base.M();
}

The behavior for base is to find the "nearest" implementation and call that implementation using a direct call, meaning if B.M is uncommented it will be called, while if it commented out then A.M will be called. Notably, if B is uncommented at compile time, but at runtime the B.M override is not present the runtime will call A.M. This is because the runtime will continue looking through base classes for a matching signature if the target method is not present.

Most importantly, the runtime does not yet have this behavior for base() calls in interface implementations. This is because there could be multiple paths to search down and the current IL encoding does not provide a root definition to search towards.

For example,

interface IA
{
    void M();
}
interface IB : IA
{
    void IA.M() // If this gets removed, the IC.M call will fail
    {
        base(IA).M();
    }
}
interface IC : IB
{
    void IA.M() => base(IB).M();
}

At the moment, we do not have the time to implement an entire new IL form for base() calls, so we have to implement a behavior in absence of that feature.

Choices:

  1. No base() call
  2. base() call can only target the original definition of the method
  3. base(T).M() call is a direct call to T and an error if the method doesn't exist
  4. base(T) starts searching in T, but in the compiler looks at the bases for a unique, most derived implementation.

For feature evolution, we then have three more choices later.

  1. Stay as-is
  2. New opcode/behavior for base()
  3. New opcode/behavior for base. and base()

Conclusion

For the first choice, let's do (3). The emitted code will be a direct call to that method. It is expected that the runtime will throw an exception if an implementation is not present in that type. For binding, in classes the signature used will be the closest override, while for interfaces the signature will be the member definition.