Description
This is an explanatory summary of the proposed default interface methods feature for C#, intended to lead the LDM through an understanding of the proposed feature, with examples, and to guide the discussion. We present the feature as it applies to methods, but the intent is that is also applies to properties and indexers. For simplicity of exposition, we confine our discussion to methods.
Open Issue: Should this also apply to events?
Similarly, it applies equally to classes and structs, but we confine our exposition to classes.
This proposal adds support for virtual extension methods - methods in interfaces with concrete implementations. A class that implements such an interface is required to have a single most specific implementation for the interface method inherited from its base classes or interfaces.
The principal motivations for this feature are
- Default interface methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.
- The feature enables C# to interoperate with APIs targeting Android (Java) and iOs (Swift), which support similar features.
- As it turns out, adding default interface implementations provides the elements of the "traits" language feature (https://en.wikipedia.org/wiki/Trait_(computer_programming)). Traits have proven to be a powerful programming technique (http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf).
(Based on the likely implementation technique) this feature requires corresponding support in the CLI/CLR. Programs that take advantage of this feature cannot run on earlier versions of the platform.
Modifiers in interfaces
Because this proposal includes modifiers that can newly be applied to methods in interfaces (private
, static
, and override
), as we will describe later, we propose that the default modifiers public
and abstract
be permitted to be explicit as well. For clarity, we sometimes use these modifiers explicitly in examples of this feature.
Open Issue: should we permit the modifiers
abstract
andpublic
on methods in interfaces, even though that is the default?
Open issue: should we permitvirtual
?
Concrete methods in interfaces
The simplest form of this feature is the ability to declare a concrete method in an interface, which is a method with a body.
interface IA
{
void M() { WriteLine("IA.M"); }
}
A class that implements this interface need not implement its concrete method.
class C : IA { } // OK
IA i = new C();
i.M(); // prints "IA.M"
The final override for IA.M
in class C
is the concrete method M
declared in IA
. Note that a class does not inherit members from its interfaces; that is not changed by this feature:
new C().M(); // error: class 'C' does not contain a member 'M'
The basic feature is particularly useful to enable evolution of existing interface types by the addition of new virtual methods.
Overrides in interfaces
An interface can override
a method declared in a base interface, with or without explicitly naming the overridden method's declaring interface
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); } // explicitly named
}
interface IC : IA
{
override void M() { WriteLine("IC.M"); } // implicitly named
}
If the interface is not named in the override declaration, then all matching methods (from direct or indirect base interfaces) are overridden. There must be at least one such method or the override declaration is an error.
Open issue: should that "direct and indirect" be "direct" here?
Overrides in interfaces are useful to provide a more specific (e.g. more efficient) implementation of a base interface's method. For example, a new First()
method on IEnumerable
may have a much more efficient implementation on the interface IList
.
A method declared in an interface is never treated as an override
of another method unless it contains he override
modifier. This is necessary for compatibility.
interface IA
{
void M();
}
interface IB : IA
{
void M(); // not related to 'IA.M'; not an override
}
Reabstraction
A virtual (concrete) method declared in an interface may be overridden to be abstract in a derived interface
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override abstract void M();
}
class C : IB { } // error: class 'C' does not implement 'IA.M'.
The abstract
modifier is not required in the declaration of IB.M
(that is the default in interfaces), but it is probably good practice to be explicit in an override declaration.
This is useful in derived interfaces where the default implementation of a method is inappropriate and a more appropriate implementation should be provided by implementing classes.
The most specific override rule
We require that every interface and class have a most specific override for every interface method among the overrides appearing in the type or its direct and indirect interfaces. If there is no override, the method itself is considered the most specific override. One override M1
is considered more specific than another override M2
if M1
is declared on type T1
, M2
is declared on type T2
, and T1
contains T2
among its direct or indirect interfaces. The most specific override is a unique override that is more specific than every other override.
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
override void IA.M() { WriteLine("IC.M"); }
}
interface ID : IB, IC { } // error: no most specific override for 'IA.M'
abstract class C : IB, IC { } // error: no most specific override for 'IA.M'
abstract class D : IA, IB, IC // ok
{
public abstract void M();
}
The most specific override rule ensures that a conflict (i.e. an ambiguity arising from diamond inheritance) is resolved explicitly by the programmer at the point where the conflict arises.
Because we support explicit abstract overrides in interfaces, we could do so in classes as well
abstract class E : IA, IB, IC // ok
{
abstract void IA.M();
}
Open issue: should we support explicit interface abstract overrides in classes?
In addition, it is an error if in a class declaration the most specific override of some interface method is an an abstract override that was declared in an interface. This is an existing rule restated using the new terminology.
interface IF
{
void M();
}
abstract class F : IF { } // error: 'F' does not implement 'IF.M'
static
and private
methods
Because interfaces may now contain executable code, it is useful to abstract common code into private and static methods. We now permit these in interfaces.
Open issue: Should we support private methods? Should we support static methods?
Open issue: should we permit interface methods to be
protected
orinternal
or other access? If so, what are the semantics? Are theyvirtual
by default? If so, is there a way to make them non-virtual?
Open issue: If we support static methods, should we support (static) operators?
Base interface invocations
An instance (nonstatic) method is permitted to invoke an accessible instance method override in a direct base interface nonvirtually by naming it using the syntax Type.base.M
. This is useful when an override that is required to be provided due to diamond inheritance is resolved by delegating to one particular base implementation.
interface IA
{
void M() { WriteLine("IA.M"); }
}
interface IB : IA
{
override void IA.M() { WriteLine("IB.M"); }
}
interface IC : IA
{
override void IA.M() { WriteLine("IC.M"); }
}
class D : IA, IB, IC
{
void IA.M() { IB.base.M(); }
}
Open issue: what syntax should we use for base invocation?
Effect on existing programs
The rules presented here are intended to have no effect on the meaning of existing programs.
Example 1:
interface IA
{
void M();
}
class C: IA // Error: IA.M has no concrete most specific override in C
{
public static void M() { } // method unrelated to 'IA.M' because static
}
Example 2:
interface IA
{
void M();
}
class Base: IA
{
void IA.M() { }
}
class Derived: Base, IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
The same rules give similar results to the analogous situation involving default interface methods:
interface IA
{
void M() { }
}
class Derived: IA // OK, all interface members have a concrete most specific override
{
private void M() { } // method unrelated to 'IA.M' because private
}
Open issue: confirm that this is an intended consequence of the specification.
Further areas to be specified
- It would be useful to catalog the kinds of source and binary compatibility effects caused by adding default interface methods and overrides to existing interfaces.
- We need to specify the runtime method resolution rules.
/cc @dotnet/csharplangdesign @dotnet/roslyn-compiler
Activity