A Tour of Default Interface Methods for C# ("traits") #288

Closed
gafter opened this Issue Mar 19, 2017 · 431 comments

Comments

Projects
None yet
@gafter
Member

gafter commented Mar 19, 2017

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

(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 and public on methods in interfaces, even though that is the default?
Open issue: should we permit virtual?

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 or internal or other access? If so, what are the semantics? Are they virtual 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

@gafter gafter added the Discussion label Mar 19, 2017

@gafter gafter self-assigned this Mar 19, 2017

@gafter gafter added the Proposal label Mar 19, 2017

@yaakov-h

This comment has been minimized.

Show comment
Hide comment
@yaakov-h

yaakov-h Mar 19, 2017

Contributor

What are the impacts on source and binary compatibility? Would adding a new method to an interface be non-breaking if it has a default implementation?

Contributor

yaakov-h commented Mar 19, 2017

What are the impacts on source and binary compatibility? Would adding a new method to an interface be non-breaking if it has a default implementation?

@orthoxerox

This comment has been minimized.

Show comment
Hide comment
@orthoxerox

orthoxerox Mar 19, 2017

I have to think a while before I can say anything about the semantics, but I feel that IB.base.M() would look better as base<IB>.M().

I have to think a while before I can say anything about the semantics, but I feel that IB.base.M() would look better as base<IB>.M().

@AlgorithmsAreCool

This comment has been minimized.

Show comment
Hide comment
@AlgorithmsAreCool

AlgorithmsAreCool Mar 19, 2017

I'm genuinely amazed that the LDM is pushing to add multiple inheritance to C# so many years after launch. Not mad, just amazed. It feels like altering a major design principle of the language.

My layman's impression is that there will be such minor differences between classes and interfaces now it almost seems to put the two features into redundancy.

I'm genuinely amazed that the LDM is pushing to add multiple inheritance to C# so many years after launch. Not mad, just amazed. It feels like altering a major design principle of the language.

My layman's impression is that there will be such minor differences between classes and interfaces now it almost seems to put the two features into redundancy.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 19, 2017

Member

@AlgorithmsAreCool C# has had multiple inheritance in interfaces since the start. MI is problematic when you can inherit state, but this proposal does not put state into interfaces.

Member

gafter commented Mar 19, 2017

@AlgorithmsAreCool C# has had multiple inheritance in interfaces since the start. MI is problematic when you can inherit state, but this proposal does not put state into interfaces.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 19, 2017

Member

@yaakov-h yes, that question is posed in the penultimate bullet of the OP. The answer is longish and I expect it will form a new discussion issue.

Member

gafter commented Mar 19, 2017

@yaakov-h yes, that question is posed in the penultimate bullet of the OP. The answer is longish and I expect it will form a new discussion issue.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 19, 2017

Contributor

It comes down to classes carry state and interfaces only convey behavior. That eliminates much of the issue with multiple inheritance. It works fairly well in Java and ought to work better in C# as explicit overrides could help resolve ambiguities.

Since interfaces can't manage state is there a lot of purpose to default event implementations? Seems at best you could have them be no-ops, manage subscription through some globally-accessible mechanism or maybe translate the event to something expected by one of the other methods, like a callback interface. But I can't imagine that would add any additional complication to this proposal.

As for static methods, purely a question of design. VB.NET has supported that since 1.0. It's not CLS but C# allows plenty of things that aren't CLS.

Contributor

HaloFour commented Mar 19, 2017

It comes down to classes carry state and interfaces only convey behavior. That eliminates much of the issue with multiple inheritance. It works fairly well in Java and ought to work better in C# as explicit overrides could help resolve ambiguities.

Since interfaces can't manage state is there a lot of purpose to default event implementations? Seems at best you could have them be no-ops, manage subscription through some globally-accessible mechanism or maybe translate the event to something expected by one of the other methods, like a callback interface. But I can't imagine that would add any additional complication to this proposal.

As for static methods, purely a question of design. VB.NET has supported that since 1.0. It's not CLS but C# allows plenty of things that aren't CLS.

@louthy

This comment has been minimized.

Show comment
Hide comment
@louthy

louthy Mar 19, 2017

Since interfaces can't manage state is there a lot of purpose to default event implementations

EDIT: Misread the original comment. I see now it's referring to events. My bad.

@HaloFour They'd be very useful for doing ad-hoc polymorphism, i.e.:

    public interface Semigroup<A>
    {
        A Append(A x, A y);
    }

    public interface Monoid<A> : Semigroup<A> 
    {
        A Empty();

        A Concat(IEnumerable<A> xs) =>
            xs.Fold(Empty(), Append);

        A Concat(params A[] xs) =>
            xs.Fold(Empty(), Append);
    }

    public struct MString : Monoid<string>
    {
        public static readonly MString Inst = new MString();

        public string Append(string x, string y) =>
            x + y;

        public string Empty() =>
            "";
    }

    public struct MEnumerable<A> : Monoid<Enumerable<A>>
    {
        public static readonly MEnumerable<A> Inst = new MEnumerable<A>();

        public Enumerable<A> Append(Enumerable<A> x, Enumerable<A> y) =>
            Enumerable.Concat(x, y);

        public Enumerable<A> Empty() =>
            new A[0];
    }

    var x = MEnumerable<A>.Inst.Concat(new [] {  new [] { 1,2,3 }, new [] { 4,5,6 } }); // [1,2,3,4,5,6]
    var y = MString.Inst.Concat("Hello", " ", "World"); // "Hello World"

So I think this has value for the work on Shapes. And as someone who's using this technique quite extensively already, I think this work is very welcome.

louthy commented Mar 19, 2017

Since interfaces can't manage state is there a lot of purpose to default event implementations

EDIT: Misread the original comment. I see now it's referring to events. My bad.

@HaloFour They'd be very useful for doing ad-hoc polymorphism, i.e.:

    public interface Semigroup<A>
    {
        A Append(A x, A y);
    }

    public interface Monoid<A> : Semigroup<A> 
    {
        A Empty();

        A Concat(IEnumerable<A> xs) =>
            xs.Fold(Empty(), Append);

        A Concat(params A[] xs) =>
            xs.Fold(Empty(), Append);
    }

    public struct MString : Monoid<string>
    {
        public static readonly MString Inst = new MString();

        public string Append(string x, string y) =>
            x + y;

        public string Empty() =>
            "";
    }

    public struct MEnumerable<A> : Monoid<Enumerable<A>>
    {
        public static readonly MEnumerable<A> Inst = new MEnumerable<A>();

        public Enumerable<A> Append(Enumerable<A> x, Enumerable<A> y) =>
            Enumerable.Concat(x, y);

        public Enumerable<A> Empty() =>
            new A[0];
    }

    var x = MEnumerable<A>.Inst.Concat(new [] {  new [] { 1,2,3 }, new [] { 4,5,6 } }); // [1,2,3,4,5,6]
    var y = MString.Inst.Concat("Hello", " ", "World"); // "Hello World"

So I think this has value for the work on Shapes. And as someone who's using this technique quite extensively already, I think this work is very welcome.

@smoothdeveloper

This comment has been minimized.

Show comment
Hide comment
@smoothdeveloper

smoothdeveloper Mar 19, 2017

it feels the syntax for calling base (non ambiguously) should inherit from C++ or the way we disambiguate with assembly qualfier.

interface A {
  void Foo(){}
}
interface B {
  void Foo(){}
}

class C : A,B {
  override Foo(){
    A::Foo();
  }
}

it feels the syntax for calling base (non ambiguously) should inherit from C++ or the way we disambiguate with assembly qualfier.

interface A {
  void Foo(){}
}
interface B {
  void Foo(){}
}

class C : A,B {
  override Foo(){
    A::Foo();
  }
}
@lloydjatkinson

This comment has been minimized.

Show comment
Hide comment
@lloydjatkinson

lloydjatkinson Mar 19, 2017

I don't see the advantage of this over having a class that implements an interface and inherits an abstract class. This suggestion seems to make interfaces no longer just an interface, and it feels "messy".

I don't see the advantage of this over having a class that implements an interface and inherits an abstract class. This suggestion seems to make interfaces no longer just an interface, and it feels "messy".

@Jorenkv

This comment has been minimized.

Show comment
Hide comment
@Jorenkv

Jorenkv Mar 19, 2017

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'

Is this necessary? It seems pretty awful to have to cast an object just because the member you'd like to access happens to be defined in an interface instead of in the class itself.

When you're only using this feature to provide default behaviour for an interface this won't be an issue, but I'd also like to use the feature to allow code reuse without needing to derive from a class.

Jorenkv commented Mar 19, 2017

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'

Is this necessary? It seems pretty awful to have to cast an object just because the member you'd like to access happens to be defined in an interface instead of in the class itself.

When you're only using this feature to provide default behaviour for an interface this won't be an issue, but I'd also like to use the feature to allow code reuse without needing to derive from a class.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Mar 19, 2017

My thoughts on this idea:

  1. The fact that @jcouv is on twitter saying he's excited to be working on this feature, yet @gafter is talking about it being a proposal highlights the fact that comms from the team to the community are still broken. There's no LDM notes for February still, let alone Mach. The promised improved interaction between @MadsTorgersen & the team and the community still hasn't happened. The community is once again in the dark as to what the team is doing.
  2. Why is the team putting effort into this feature now? Why is it suddenly a priority? Does this mean that features that many of us are still waiting for, such as better pattern matching and records, are going to be delayed yet again?
  3. The main cited use case is for allowing interfaces to add new members without breaking existing code. This is a good use case, and the basic idea of concrete methods in interfaces, that can only be accessed via referencing that interface nicely solves that use case in a simple way. So why then does the proposal quickly become muddled and over-complicated with ideas around overriding in other interfaces, forcing back to abstract and even talk of protected and internal? What are the use cases for these complicating additions?

My thoughts on this idea:

  1. The fact that @jcouv is on twitter saying he's excited to be working on this feature, yet @gafter is talking about it being a proposal highlights the fact that comms from the team to the community are still broken. There's no LDM notes for February still, let alone Mach. The promised improved interaction between @MadsTorgersen & the team and the community still hasn't happened. The community is once again in the dark as to what the team is doing.
  2. Why is the team putting effort into this feature now? Why is it suddenly a priority? Does this mean that features that many of us are still waiting for, such as better pattern matching and records, are going to be delayed yet again?
  3. The main cited use case is for allowing interfaces to add new members without breaking existing code. This is a good use case, and the basic idea of concrete methods in interfaces, that can only be accessed via referencing that interface nicely solves that use case in a simple way. So why then does the proposal quickly become muddled and over-complicated with ideas around overriding in other interfaces, forcing back to abstract and even talk of protected and internal? What are the use cases for these complicating additions?
@lloydjatkinson

This comment has been minimized.

Show comment
Hide comment
@lloydjatkinson

lloydjatkinson Mar 19, 2017

The main cited use case is for allowing interfaces to add new members without breaking existing code. This is a good use case, and the basic idea of concrete methods in interfaces, that can only be accessed via referencing that interface nicely solves that use case in a simple way.

Personally I disagree that they are a good use case. Interfaces are meant to be a contract, not an implementation. So why now should implementation be added to an interface?

The argument about not breaking existing code I do not feel has a lot of weight. Having to make breaking changes to your own code is just part of the process of refactoring. This proposal seems like a huge workaround/"hack" just to avoid having to update your implementations of an interface.

lloydjatkinson commented Mar 19, 2017

The main cited use case is for allowing interfaces to add new members without breaking existing code. This is a good use case, and the basic idea of concrete methods in interfaces, that can only be accessed via referencing that interface nicely solves that use case in a simple way.

Personally I disagree that they are a good use case. Interfaces are meant to be a contract, not an implementation. So why now should implementation be added to an interface?

The argument about not breaking existing code I do not feel has a lot of weight. Having to make breaking changes to your own code is just part of the process of refactoring. This proposal seems like a huge workaround/"hack" just to avoid having to update your implementations of an interface.

@ilexp

This comment has been minimized.

Show comment
Hide comment
@ilexp

ilexp Mar 19, 2017

I'm not against evolving interfaces as C# grows, but this proposal seems to overcomplicate things on the user side. Interfaces right now are a very simple concept, and I'd consider this a plus that is worth keeping.

ilexp commented Mar 19, 2017

I'm not against evolving interfaces as C# grows, but this proposal seems to overcomplicate things on the user side. Interfaces right now are a very simple concept, and I'd consider this a plus that is worth keeping.

@jmazouri

This comment has been minimized.

Show comment
Hide comment
@jmazouri

jmazouri Mar 19, 2017

Personally, I would much prefer an implementation of #164 rather than muddying interface definitions with concrete implementations.

Personally, I would much prefer an implementation of #164 rather than muddying interface definitions with concrete implementations.

@mgarrettm

This comment has been minimized.

Show comment
Hide comment
@mgarrettm

mgarrettm Mar 19, 2017

Completely agree with @jmazouri, expanded extension methods would be a much cleaner way of addressing the cited use case, rather than compromising the intention of interfaces.

Completely agree with @jmazouri, expanded extension methods would be a much cleaner way of addressing the cited use case, rather than compromising the intention of interfaces.

@AlgorithmsAreCool

This comment has been minimized.

Show comment
Hide comment
@AlgorithmsAreCool

AlgorithmsAreCool Mar 19, 2017

@DavidArno
Lets hold off on oiling the pitchforks for a little while, i think there is just some enthusiasm for this feature from a few team members and they are putting together a prototype to sell to the LDM. Any of us could do that with our proposals if we had the time and expertise.

Thinking about this more, I don't think this is a good idea. It solves a narrow problem, and in the process it creates confusion in the core design of the language without great benefit. C# has made all this way with very limited MI via interfaces, so why does it need attention now?

@DavidArno
Lets hold off on oiling the pitchforks for a little while, i think there is just some enthusiasm for this feature from a few team members and they are putting together a prototype to sell to the LDM. Any of us could do that with our proposals if we had the time and expertise.

Thinking about this more, I don't think this is a good idea. It solves a narrow problem, and in the process it creates confusion in the core design of the language without great benefit. C# has made all this way with very limited MI via interfaces, so why does it need attention now?

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

C# has made all this way with very limited MI via interfaces, so why does it need attention now?

We could literally make thta argument about any feature :) After all, every feature is one that we made it all this way without doing.

The simple fact of the matter is that this has been an issue with .Net for a very long time. To the point that working with interfaces can be quite a struggle. We've felt this pain in the .Net APIs themselves as well as through many other APIs that MS has exposed.

This is an area we've definitely wanted to improve things in in the past, but not all the right pieces were in place for that to happen, or other things were felt to be more important. Now it's the case that we think we could do it, and we still think this would be super helpful and valuable for our surrounding ecosystem.

C# has made all this way with very limited MI via interfaces, so why does it need attention now?

We could literally make thta argument about any feature :) After all, every feature is one that we made it all this way without doing.

The simple fact of the matter is that this has been an issue with .Net for a very long time. To the point that working with interfaces can be quite a struggle. We've felt this pain in the .Net APIs themselves as well as through many other APIs that MS has exposed.

This is an area we've definitely wanted to improve things in in the past, but not all the right pieces were in place for that to happen, or other things were felt to be more important. Now it's the case that we think we could do it, and we still think this would be super helpful and valuable for our surrounding ecosystem.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Mar 19, 2017

Contributor

This has been a useful feature in Java. It was their answer to extension methods, and it does overlap them a little from a use case basis. But it also enables a couple of different scenarios. Being properly virtual they allow for type-specific implementations that tolerate casting without having to bake implementation-specific dispatch into the extension method.

I think there is room for both in the language. From a consumer point of view it's no different than working with any other interface. From an implementer's perspective it makes things easier as the number of required members decreases. IEnumerator<T> could finally be reduced to the two members that the vast majority of implementations actually implement (or at least make it so that I don't have to explicitly implement IEnumerator.Current.)

Contributor

HaloFour commented Mar 19, 2017

This has been a useful feature in Java. It was their answer to extension methods, and it does overlap them a little from a use case basis. But it also enables a couple of different scenarios. Being properly virtual they allow for type-specific implementations that tolerate casting without having to bake implementation-specific dispatch into the extension method.

I think there is room for both in the language. From a consumer point of view it's no different than working with any other interface. From an implementer's perspective it makes things easier as the number of required members decreases. IEnumerator<T> could finally be reduced to the two members that the vast majority of implementations actually implement (or at least make it so that I don't have to explicitly implement IEnumerator.Current.)

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

The fact that @jcouv is on twitter saying he's excited to be working on this feature, yet @gafter is talking about it being a proposal highlights the fact that comms from the team to the community are still broken.

As we've said on our home page:

In many cases it will be necessary to implement and share a prototype of a feature in order to land on the right design, and ultimately decide whether to adopt the feature. Prototypes help discover both implementation and usability issues of a feature. A prototype should be implemented in a fork of the Roslyn repo and meet the following bar:

The fact that @jcouv is on twitter saying he's excited to be working on this feature, yet @gafter is talking about it being a proposal highlights the fact that comms from the team to the community are still broken.

As we've said on our home page:

In many cases it will be necessary to implement and share a prototype of a feature in order to land on the right design, and ultimately decide whether to adopt the feature. Prototypes help discover both implementation and usability issues of a feature. A prototype should be implemented in a fork of the Roslyn repo and meet the following bar:

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

This has been a useful feature in Java. It was their answer to extension methods, and it does overlap them a little from a use case basis. But it also enables a couple of different scenarios. Being properly virtual they allow for type-specific implementations that tolerate casting without having to bake implementation-specific dispatch into the extension method.

Yup. And this has been an issue for us in our own APIs. For example, a lot of linq extension methods optimize for the IList case. But they don't work properly on IReadOnlyList. That's unfortunate. Extensions have carried us far. But we see cracks there and we think we have an idea about how we can create a good system that solves another set of issues well.

I think there is room for both in the language. From a consumer point of view it's no different than working with any other interface. From an implementer's perspective it makes things easier as the number of required members decreases. IEnumerator could finally be reduced to the two members that the vast majority of implementations actually implement (or at least make it so that I don't have to explicitly implement IEnumerator.Current.)

Yup. It would also just be great for working with interfaces in APIs today. Right now you have to do the "IFoo1, IFoo2, ... IFooX" route for interfaces in order to add members to them. It's super unpleasant and it would be great if we could safely add members to interfaces without that being a massive breaking change like it is today.

This has been a useful feature in Java. It was their answer to extension methods, and it does overlap them a little from a use case basis. But it also enables a couple of different scenarios. Being properly virtual they allow for type-specific implementations that tolerate casting without having to bake implementation-specific dispatch into the extension method.

Yup. And this has been an issue for us in our own APIs. For example, a lot of linq extension methods optimize for the IList case. But they don't work properly on IReadOnlyList. That's unfortunate. Extensions have carried us far. But we see cracks there and we think we have an idea about how we can create a good system that solves another set of issues well.

I think there is room for both in the language. From a consumer point of view it's no different than working with any other interface. From an implementer's perspective it makes things easier as the number of required members decreases. IEnumerator could finally be reduced to the two members that the vast majority of implementations actually implement (or at least make it so that I don't have to explicitly implement IEnumerator.Current.)

Yup. It would also just be great for working with interfaces in APIs today. Right now you have to do the "IFoo1, IFoo2, ... IFooX" route for interfaces in order to add members to them. It's super unpleasant and it would be great if we could safely add members to interfaces without that being a massive breaking change like it is today.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

Why is the team putting effort into this feature now?

Because it's something we've wanted to make better for years. And we think we may be able to. So we'd like to learn more so we can effectively design the best feature.

Why is it suddenly a priority?

Because we looked at the set of work we could do, and we thought this made the cut. That's what we do with every language release.

Does this mean that features that many of us are still waiting for, such as better pattern matching and records, are going to be delayed yet again?

Maybe. Maybe not. We're looking at a lot of different things and a lot of different areas for our next set of releases. I'm sure that every single thing we work on will be in an "i don't care" group for some set of customers :)

Why is the team putting effort into this feature now?

Because it's something we've wanted to make better for years. And we think we may be able to. So we'd like to learn more so we can effectively design the best feature.

Why is it suddenly a priority?

Because we looked at the set of work we could do, and we thought this made the cut. That's what we do with every language release.

Does this mean that features that many of us are still waiting for, such as better pattern matching and records, are going to be delayed yet again?

Maybe. Maybe not. We're looking at a lot of different things and a lot of different areas for our next set of releases. I'm sure that every single thing we work on will be in an "i don't care" group for some set of customers :)

@AlgorithmsAreCool

This comment has been minimized.

Show comment
Hide comment
@AlgorithmsAreCool

AlgorithmsAreCool Mar 19, 2017

We could literally make thta argument about any feature :) After all, every feature is one that we made it all this way without doing.

Yeah, as soon as I typed that I regretted it 😆

That being said, if shapes or shapes+records would cover the same conceptual ground as this proposal (which i am fairly sure shapes would), I would take shapes in a heartbeat over this. Half of a heartbeat even.

We could literally make thta argument about any feature :) After all, every feature is one that we made it all this way without doing.

Yeah, as soon as I typed that I regretted it 😆

That being said, if shapes or shapes+records would cover the same conceptual ground as this proposal (which i am fairly sure shapes would), I would take shapes in a heartbeat over this. Half of a heartbeat even.

@Eirenarch

This comment has been minimized.

Show comment
Hide comment
@Eirenarch

Eirenarch Mar 19, 2017

If this is implemented what would be the use case for extension methods over default methods?

If this is implemented what would be the use case for extension methods over default methods?

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

If this is implemented what would be the use case for extension methods over default methods?

You could still only provide a default-method if you were the author of the interface. They can't be added by someone else. Extension methods don't have that limitation. They can be added externally.

If this is implemented what would be the use case for extension methods over default methods?

You could still only provide a default-method if you were the author of the interface. They can't be added by someone else. Extension methods don't have that limitation. They can be added externally.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 19, 2017

Extension methods can also do things like specifically extend specific instantiations. i.e. i can have an extension method specifically for the type IList<int>. That's not possible with default interface members.

Extension methods can also do things like specifically extend specific instantiations. i.e. i can have an extension method specifically for the type IList<int>. That's not possible with default interface members.

@jamesqo

This comment has been minimized.

Show comment
Hide comment
@jamesqo

jamesqo Mar 19, 2017

Contributor

:O I linked to this issue on reddit because I thought it was exciting. Surprised to see people are generally against it rather than in favor of it.

Contributor

jamesqo commented Mar 19, 2017

:O I linked to this issue on reddit because I thought it was exciting. Surprised to see people are generally against it rather than in favor of it.

@yaakov-h

This comment has been minimized.

Show comment
Hide comment
@yaakov-h

yaakov-h Mar 19, 2017

Contributor

I don't mind making interface methods optional (a la Objective-C, or even something else entirely), but providing a hard implementation - even a default one - seems more like multiple inheritance and less like providing an interface.

Contributor

yaakov-h commented Mar 19, 2017

I don't mind making interface methods optional (a la Objective-C, or even something else entirely), but providing a hard implementation - even a default one - seems more like multiple inheritance and less like providing an interface.

@bbarry

This comment has been minimized.

Show comment
Hide comment
@bbarry

bbarry Mar 20, 2017

Contributor

I somewhat like the idea of interfaces being able to implement public and private methods, but I am less enthusiastic about interfaces overriding methods from other interfaces and protected methods (complex inheritance trees quickly become a nasty mess).

Considering the broader picture... I'm assuming since properties are being brought along, you are only going to bring properties which don't have backing state into this? That is to say it would still be a breaking change to add a property to an interface with a default get/set. I think I am in a dislike state here. Thinking that I would really want #133, I lean further into dislike.

That said, it makes sense to implement all of that in a single CLR change. Maybe it is worth implementing everything necessary at the CLR level, but only some of it in C#?

Contributor

bbarry commented Mar 20, 2017

I somewhat like the idea of interfaces being able to implement public and private methods, but I am less enthusiastic about interfaces overriding methods from other interfaces and protected methods (complex inheritance trees quickly become a nasty mess).

Considering the broader picture... I'm assuming since properties are being brought along, you are only going to bring properties which don't have backing state into this? That is to say it would still be a breaking change to add a property to an interface with a default get/set. I think I am in a dislike state here. Thinking that I would really want #133, I lean further into dislike.

That said, it makes sense to implement all of that in a single CLR change. Maybe it is worth implementing everything necessary at the CLR level, but only some of it in C#?

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Mar 20, 2017

I really don't like this.

The reason why Java needs default methods is that they don't use interfaces correctly. Rather than keeping them small and to the point, they bloat them with excessive amounts of methods in a pointless attempt to avoid working with "concrete classes".

So far .NET has mostly managed to avoid that. There are a couple problem spots such as the interfaces in System.Data, but by and large we're using abstract interfaces correctly and thus don't need this feature.

I hate to be "that guy", but I really think this is just going to open the door to bad practices. It will eliminate the semantic difference between abstract classes and abstract interfaces and we're just going to see a ton of abstract classes implemented with the interface keyword.

I really don't like this.

The reason why Java needs default methods is that they don't use interfaces correctly. Rather than keeping them small and to the point, they bloat them with excessive amounts of methods in a pointless attempt to avoid working with "concrete classes".

So far .NET has mostly managed to avoid that. There are a couple problem spots such as the interfaces in System.Data, but by and large we're using abstract interfaces correctly and thus don't need this feature.

I hate to be "that guy", but I really think this is just going to open the door to bad practices. It will eliminate the semantic difference between abstract classes and abstract interfaces and we're just going to see a ton of abstract classes implemented with the interface keyword.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Mar 20, 2017

Backwards compatibility is a huge issue here.

Say you add a new default method to an interface. But a class already has a matching method that does something completely different. What happens?

Does the class implicitly override the default method without have to be recompiled? If no, that means recompiling without changing the code changes the behavior. If yes, that may result in incorrect behavior if the two methods mean different things and just happen to have the same name.

We avoid this for class inheritance by insisting that methods can only override other methods explicitly. Eliminating the "brittle base class" problem that Java has.

But it we do that for C#, then we have some interface methods that require overrides and some that do not. Besides being confusing, it means adding a default implementation later is also a breaking change.

Backwards compatibility is a huge issue here.

Say you add a new default method to an interface. But a class already has a matching method that does something completely different. What happens?

Does the class implicitly override the default method without have to be recompiled? If no, that means recompiling without changing the code changes the behavior. If yes, that may result in incorrect behavior if the two methods mean different things and just happen to have the same name.

We avoid this for class inheritance by insisting that methods can only override other methods explicitly. Eliminating the "brittle base class" problem that Java has.

But it we do that for C#, then we have some interface methods that require overrides and some that do not. Besides being confusing, it means adding a default implementation later is also a breaking change.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Mar 20, 2017

By considering protected and internal methods, it's pretty clear that we're no longer talking about default methods and have gone 90% of the way to multiple inheritance.

If you really want multiple inheritance, be up front about it. Don't try to sneak it in as part of abstract interfaces; make it a first class feature.

By considering protected and internal methods, it's pretty clear that we're no longer talking about default methods and have gone 90% of the way to multiple inheritance.

If you really want multiple inheritance, be up front about it. Don't try to sneak it in as part of abstract interfaces; make it a first class feature.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Mar 20, 2017

I don't see why static methods are part of this proposal.

Unlike the rest of it, I think static methods would be great. Especially when combined with extension methods. It is just an organizational convenience so we don't need a separate static class FooExtension to hold them. It doesn't actually change any semantics or introduce any danger in terms of complexity or backwards compatibility.

I don't see why static methods are part of this proposal.

Unlike the rest of it, I think static methods would be great. Especially when combined with extension methods. It is just an organizational convenience so we don't need a separate static class FooExtension to hold them. It doesn't actually change any semantics or introduce any danger in terms of complexity or backwards compatibility.

@Grauenwolf

This comment has been minimized.

Show comment
Hide comment
@Grauenwolf

Grauenwolf Mar 20, 2017

For example, a lot of linq extension methods optimize for the IList case. But they don't work properly on IReadOnlyList.

That's because IList doesn't inherit from IReadOnlyList.

But I don't see how this change would fix that problem.

For example, a lot of linq extension methods optimize for the IList case. But they don't work properly on IReadOnlyList.

That's because IList doesn't inherit from IReadOnlyList.

But I don't see how this change would fix that problem.

@jamesqo

This comment has been minimized.

Show comment
Hide comment
@jamesqo

jamesqo Mar 20, 2017

Contributor

I hate to be "that guy", but I really think this is just going to open the door to bad practices. It will eliminate the semantic difference between abstract classes and abstract interfaces and we're just going to see a ton of abstract classes implemented with the interface keyword.

Abstract classes should be used when the base class contains fields, or may contain fields in the future. An interface with default methods would be used when you know that the behavior of the default methods would be determined purely by the behavior of the implemented methods.

Contributor

jamesqo commented Mar 20, 2017

I hate to be "that guy", but I really think this is just going to open the door to bad practices. It will eliminate the semantic difference between abstract classes and abstract interfaces and we're just going to see a ton of abstract classes implemented with the interface keyword.

Abstract classes should be used when the base class contains fields, or may contain fields in the future. An interface with default methods would be used when you know that the behavior of the default methods would be determined purely by the behavior of the implemented methods.

@jamesqo

This comment has been minimized.

Show comment
Hide comment
@jamesqo

jamesqo Mar 20, 2017

Contributor

By considering protected and internal methods, it's pretty clear that we're no longer talking about default methods and have gone 90% of the way to multiple inheritance.

If you really want multiple inheritance, be up front about it. Don't try to sneak it in as part of abstract interfaces; make it a first class feature.

We don't have the diamond inheritance problem due to the most specific override rule, which is the main caveat of multiple inheritance.

Contributor

jamesqo commented Mar 20, 2017

By considering protected and internal methods, it's pretty clear that we're no longer talking about default methods and have gone 90% of the way to multiple inheritance.

If you really want multiple inheritance, be up front about it. Don't try to sneak it in as part of abstract interfaces; make it a first class feature.

We don't have the diamond inheritance problem due to the most specific override rule, which is the main caveat of multiple inheritance.

@jamesqo

This comment has been minimized.

Show comment
Hide comment
@jamesqo

jamesqo Mar 20, 2017

Contributor

Unlike the rest of it, I think static methods would be great.

I agree with you here, it would be nice to be able to have static factory methods to create instances of interfaces. Would probably help with decoupling, e.g. you're calling IFoo.Create() which returns an IFoo, as opposed to Foo.Create() which returns a Foo, and you can change IFoo.Create() to return whatever kind of type you want without breaking code so long as it inherits from IFoo.

Contributor

jamesqo commented Mar 20, 2017

Unlike the rest of it, I think static methods would be great.

I agree with you here, it would be nice to be able to have static factory methods to create instances of interfaces. Would probably help with decoupling, e.g. you're calling IFoo.Create() which returns an IFoo, as opposed to Foo.Create() which returns a Foo, and you can change IFoo.Create() to return whatever kind of type you want without breaking code so long as it inherits from IFoo.

@CyrusNajmabadi

This comment has been minimized.

Show comment
Hide comment
@CyrusNajmabadi

CyrusNajmabadi Mar 20, 2017

But I don't see how this change would fix that problem.

The point would have been that we could just have added members to IEnumerable<T> initially, and then provided specializations per implementation. For example, instead of "Count" being an extension method, it could have been a default interface method:

interface IEnumerable<T>
{
    int Count()
    {
        int count = 0;
        foreach (var x in this)
        {
             count++;
        }

        return count;
    }
}

interface IReadOnlyList<T> ...
{
    int Count { get; }
    override int IEnumerable<T>.Count() => this.Count;
}

interface IList<T> ...
{
    int Count { get; }
    override int IEnumerable<T>.Count() => this.Count;
}

Right now the extension-method based approach can only accomplish the above by doing an explicit type-check against the stream it is passed.

CyrusNajmabadi commented Mar 20, 2017

But I don't see how this change would fix that problem.

The point would have been that we could just have added members to IEnumerable<T> initially, and then provided specializations per implementation. For example, instead of "Count" being an extension method, it could have been a default interface method:

interface IEnumerable<T>
{
    int Count()
    {
        int count = 0;
        foreach (var x in this)
        {
             count++;
        }

        return count;
    }
}

interface IReadOnlyList<T> ...
{
    int Count { get; }
    override int IEnumerable<T>.Count() => this.Count;
}

interface IList<T> ...
{
    int Count { get; }
    override int IEnumerable<T>.Count() => this.Count;
}

Right now the extension-method based approach can only accomplish the above by doing an explicit type-check against the stream it is passed.

@chetan0213

This comment has been minimized.

Show comment
Hide comment
@chetan0213

chetan0213 Nov 10, 2017

Will this mean that now we will be able to create an instance of the interface as well, as the interface might have some default implementation and if so then they (only members with default implementation) should be accessible if we use Activator.CreateInstance to create the instance of the interface without implementing it anywhere.

Also looking at this makes the difference between the abstract class and the Interface even more thinner. There are only handful of cases were I see this feature will be useful but in most cases I feel like it will be redundant feature unless we also allow to create an instance of interface without implementation(if interface has some default members with default implementation) which will take it to the next level.

Mostly we are talking about methods here but how will the implementation of default properties/indexers will work here.

chetan0213 commented Nov 10, 2017

Will this mean that now we will be able to create an instance of the interface as well, as the interface might have some default implementation and if so then they (only members with default implementation) should be accessible if we use Activator.CreateInstance to create the instance of the interface without implementing it anywhere.

Also looking at this makes the difference between the abstract class and the Interface even more thinner. There are only handful of cases were I see this feature will be useful but in most cases I feel like it will be redundant feature unless we also allow to create an instance of interface without implementation(if interface has some default members with default implementation) which will take it to the next level.

Mostly we are talking about methods here but how will the implementation of default properties/indexers will work here.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Nov 10, 2017

Contributor

@chetan0213

No, you won't be able to create an instance of the interface. The interface itself would have no constructor.

Contributor

HaloFour commented Nov 10, 2017

@chetan0213

No, you won't be able to create an instance of the interface. The interface itself would have no constructor.

@jnm2

This comment has been minimized.

Show comment
Hide comment
@jnm2

jnm2 Nov 10, 2017

Contributor

Will this mean that now we will be able to create an instance of the interface as well

No. You can't declare a constructor. And even if you could, abstract classes have constructors and can't be instantiated.

Contributor

jnm2 commented Nov 10, 2017

Will this mean that now we will be able to create an instance of the interface as well

No. You can't declare a constructor. And even if you could, abstract classes have constructors and can't be instantiated.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Nov 10, 2017

@chetan0213,

And even if you could declare a constructor on an interface and create an instance of it, what use would it be to you? Interfaces, even with default member implementations, will not support fields; they will be stateless.

@chetan0213,

And even if you could declare a constructor on an interface and create an instance of it, what use would it be to you? Interfaces, even with default member implementations, will not support fields; they will be stateless.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 10, 2017

Member

To those suggesting this is a bad idea, perhaps you could point to all of the horrible things that have happened to Java because they made a change like this.

Member

gafter commented Nov 10, 2017

To those suggesting this is a bad idea, perhaps you could point to all of the horrible things that have happened to Java because they made a change like this.

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Nov 10, 2017

JEE? Oh no, that happened before Java 8. 😀

But seriously, @gafter, please don't invite us to start the arguments again. The decision is made and we've moved on.

JEE? Oh no, that happened before Java 8. 😀

But seriously, @gafter, please don't invite us to start the arguments again. The decision is made and we've moved on.

@safakgur

This comment has been minimized.

Show comment
Hide comment
@safakgur

safakgur Nov 23, 2017

Is there anything explaining the motivation behind private and static interface members?

Interfaces are public contracts. It's fine if the default methods can access only this public surface (like extension methods). I also see the value of not having to implement non-generic IEnumerable to implement IEnumerable<T>, for instance but I can't see what value non-public members can add. The implementer of an interface, IMHO, should only be responsible for providing the public contract specified, however they see fit. Private members can also be abused to simulate state.

And specifying the static members of a type seems like the responsibility of shapes, not interfaces. The thing that bothers me in additions like this is the blurring of lines between features and lack of clear guidelines specifying which to use when.

I know you guys are smarter than me and this, contrary to what it looks like, is not a rant. I'm just asking if you can provide more input on motivation. What is it that we can't do or can only do with hacks/workarounds, that non-public or static interface methods will allow us to do better? How do these features raise from the famous -100 to what they currently are?

Is there anything explaining the motivation behind private and static interface members?

Interfaces are public contracts. It's fine if the default methods can access only this public surface (like extension methods). I also see the value of not having to implement non-generic IEnumerable to implement IEnumerable<T>, for instance but I can't see what value non-public members can add. The implementer of an interface, IMHO, should only be responsible for providing the public contract specified, however they see fit. Private members can also be abused to simulate state.

And specifying the static members of a type seems like the responsibility of shapes, not interfaces. The thing that bothers me in additions like this is the blurring of lines between features and lack of clear guidelines specifying which to use when.

I know you guys are smarter than me and this, contrary to what it looks like, is not a rant. I'm just asking if you can provide more input on motivation. What is it that we can't do or can only do with hacks/workarounds, that non-public or static interface methods will allow us to do better? How do these features raise from the famous -100 to what they currently are?

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Nov 23, 2017

Contributor

@safakgur

The CLR already supports static members on interfaces and VB.NET has always allowed them. Lifting that restriction would be a very minor change which is why I think it's up for consideration, along with a lot of other spaghetti involving interfaces. Java also allows static methods on interfaces where they are often used to provide factory helper methods for creating instances of an implementation of that interface. For example, in Java 9 they added a bunch of helper methods to create simple implementations of immutable collections:

//  creates an immutable implementation of the List interface
List<String> list = List.of("Hello", "World"); 
Contributor

HaloFour commented Nov 23, 2017

@safakgur

The CLR already supports static members on interfaces and VB.NET has always allowed them. Lifting that restriction would be a very minor change which is why I think it's up for consideration, along with a lot of other spaghetti involving interfaces. Java also allows static methods on interfaces where they are often used to provide factory helper methods for creating instances of an implementation of that interface. For example, in Java 9 they added a bunch of helper methods to create simple implementations of immutable collections:

//  creates an immutable implementation of the List interface
List<String> list = List.of("Hello", "World"); 
@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Nov 23, 2017

Member

Private members are needed to abstract out common code in other, public, members. Java discovered and filled that need in a later release than when they added default interface methods.

Member

gafter commented Nov 23, 2017

Private members are needed to abstract out common code in other, public, members. Java discovered and filled that need in a later release than when they added default interface methods.

@fschmied

This comment has been minimized.

Show comment
Hide comment
@fschmied

fschmied Nov 24, 2017

@GeirGrusom

This comment has been minimized.

Show comment
Hide comment
@GeirGrusom

GeirGrusom Nov 24, 2017

(it's meant to be a contract!)

I don't get this argument. Lots of contracts has code. Code contracts for example. Block chain contracts. Written contracts that states the one party must dance the chicken dance.

What's the problem with adding code to interfaces?

(it's meant to be a contract!)

I don't get this argument. Lots of contracts has code. Code contracts for example. Block chain contracts. Written contracts that states the one party must dance the chicken dance.

What's the problem with adding code to interfaces?

@Joe4evr

This comment has been minimized.

Show comment
Hide comment
@Joe4evr

Joe4evr Nov 24, 2017

The CLR already supports static members on interfaces and VB.NET has always allowed them.

Maybe I'm not doing something right, but I can't seem to make this work.

What's the problem with adding code to interfaces?

The argument for them is that the abstraction is supposed to remain "pure", so that it can stay implementation-agnostic.

The hole in the argument is that there's no reason that a piece of code can't still be implementation-agnostic, especially when that code can always be overridden to use implementation-specific optimizations.

Joe4evr commented Nov 24, 2017

The CLR already supports static members on interfaces and VB.NET has always allowed them.

Maybe I'm not doing something right, but I can't seem to make this work.

What's the problem with adding code to interfaces?

The argument for them is that the abstraction is supposed to remain "pure", so that it can stay implementation-agnostic.

The hole in the argument is that there's no reason that a piece of code can't still be implementation-agnostic, especially when that code can always be overridden to use implementation-specific optimizations.

@Ubloobok

This comment has been minimized.

Show comment
Hide comment
@Ubloobok

Ubloobok Dec 24, 2017

Having principles is great but they don't solve real world problems.

@eyalsk , but, the all our work "programming" - it's about having principles.
So, as already was mentioned here - there're no stong cons for default implementation in interfaces. Now. But I'm sure that it's a way to the multiple inheritance - one of the worst things in the development.

Having principles is great but they don't solve real world problems.

@eyalsk , but, the all our work "programming" - it's about having principles.
So, as already was mentioned here - there're no stong cons for default implementation in interfaces. Now. But I'm sure that it's a way to the multiple inheritance - one of the worst things in the development.

@HaloFour

This comment has been minimized.

Show comment
Hide comment
@HaloFour

HaloFour Dec 24, 2017

Contributor

@Ubloobok

But I'm sure that it's a way to the multiple inheritance - one of the worst things in the development.

Can you explain why it is "one of the worst things in the development"? Or why this proposal in particular has the same faults as multiple inheritance?

Contributor

HaloFour commented Dec 24, 2017

@Ubloobok

But I'm sure that it's a way to the multiple inheritance - one of the worst things in the development.

Can you explain why it is "one of the worst things in the development"? Or why this proposal in particular has the same faults as multiple inheritance?

@eyalsk

This comment has been minimized.

Show comment
Hide comment
@eyalsk

eyalsk Dec 24, 2017

Contributor

@Ubloobok Principles exists to guide you but they aren't sacred when they impose a problem or limitation there's nothing wrong by breaking them as long as the reason is sufficient to do so and in this case we can say many things about it but one thing which is offer an alternative that doesn't break existing code and solve the problem.

Contributor

eyalsk commented Dec 24, 2017

@Ubloobok Principles exists to guide you but they aren't sacred when they impose a problem or limitation there's nothing wrong by breaking them as long as the reason is sufficient to do so and in this case we can say many things about it but one thing which is offer an alternative that doesn't break existing code and solve the problem.

@GeirGrusom

This comment has been minimized.

Show comment
Hide comment
@GeirGrusom

GeirGrusom Dec 25, 2017

I dislike when people want to block a feature because they are afraid of how someone else might use it. When was that ever a good argument? I've encountered inheritance implemented with dictionaries in production code but that's hardly an argument against hashmaps.

I dislike when people want to block a feature because they are afraid of how someone else might use it. When was that ever a good argument? I've encountered inheritance implemented with dictionaries in production code but that's hardly an argument against hashmaps.

@Ubloobok

This comment has been minimized.

Show comment
Hide comment
@Ubloobok

Ubloobok Dec 25, 2017

@HaloFour, because multiple inheritance notable increase complexity of the application, you'll get additional thing that requires a complexity management, that makes your code not as clear and simple as possible.

@GeirGrusom , sorry, but I strongly like the "simplicity" in the code and software design (ref to te Simple-Made-Easy).

So, I think that boxes with argues are already empty from the both sides. :)
Only time will tell results of this feature for our C#.

Ubloobok commented Dec 25, 2017

@HaloFour, because multiple inheritance notable increase complexity of the application, you'll get additional thing that requires a complexity management, that makes your code not as clear and simple as possible.

@GeirGrusom , sorry, but I strongly like the "simplicity" in the code and software design (ref to te Simple-Made-Easy).

So, I think that boxes with argues are already empty from the both sides. :)
Only time will tell results of this feature for our C#.

@eyalsk

This comment has been minimized.

Show comment
Hide comment
@eyalsk

eyalsk Dec 25, 2017

Contributor

@Ubloobok You clearly not going to use MI so where is the issue exactly?

because multiple inheritance notable increase complexity of the application

Would you mind elaborating on this and give me a real world example? otherwise, it sounds like throwing statements in the air.

you'll get additional thing that requires a complexity management

Again, I don't understand this, what do you mean by "complexity management"?

that makes your code not as clear and simple as possible.

Complex code can definitely be clear and easy to follow so once again what are you trying to say here?

Contributor

eyalsk commented Dec 25, 2017

@Ubloobok You clearly not going to use MI so where is the issue exactly?

because multiple inheritance notable increase complexity of the application

Would you mind elaborating on this and give me a real world example? otherwise, it sounds like throwing statements in the air.

you'll get additional thing that requires a complexity management

Again, I don't understand this, what do you mean by "complexity management"?

that makes your code not as clear and simple as possible.

Complex code can definitely be clear and easy to follow so once again what are you trying to say here?

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Dec 25, 2017

Member

@Ubloobok Please see http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf for an explanation for why this proposed language feature does not result in the complexity associated with generalized multiple inheritance. The author calls this feature traits. The abstract reads

Despite the undisputed prominence of inheritance as the fundamental
reuse mechanism in object-oriented programming languages, the main variants —
single inheritance, multiple inheritance, and mixin inheritance — all suffer from
conceptual and practical problems. In the first part of this paper, we identify and
illustrate these problems. We then present traits, a simple compositional model
for structuring object-oriented programs. A trait is essentially a group of pure
methods that serves as a building block for classes and is a primitive unit of
code reuse. In this model, classes are composed from a set of traits by specifying
glue code that connects the traits together and accesses the necessary state. We
demonstrate how traits overcome the problems arising from the different variants
of inheritance, we discuss how traits can be implemented effectively, and we
summarize our experience applying traits to refactor an existing class hierarchy.

This is not to say that you cannot use this (or any other) language feature to produce a mess. You can write a mess with or without default interface methods.

Member

gafter commented Dec 25, 2017

@Ubloobok Please see http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf for an explanation for why this proposed language feature does not result in the complexity associated with generalized multiple inheritance. The author calls this feature traits. The abstract reads

Despite the undisputed prominence of inheritance as the fundamental
reuse mechanism in object-oriented programming languages, the main variants —
single inheritance, multiple inheritance, and mixin inheritance — all suffer from
conceptual and practical problems. In the first part of this paper, we identify and
illustrate these problems. We then present traits, a simple compositional model
for structuring object-oriented programs. A trait is essentially a group of pure
methods that serves as a building block for classes and is a primitive unit of
code reuse. In this model, classes are composed from a set of traits by specifying
glue code that connects the traits together and accesses the necessary state. We
demonstrate how traits overcome the problems arising from the different variants
of inheritance, we discuss how traits can be implemented effectively, and we
summarize our experience applying traits to refactor an existing class hierarchy.

This is not to say that you cannot use this (or any other) language feature to produce a mess. You can write a mess with or without default interface methods.

@felixhirt

This comment has been minimized.

Show comment
Hide comment
@felixhirt

felixhirt Jan 4, 2018

I think Traits would be a very useful language feature, so i am looking forward to this. Of course it can be abused, but i guess this is true for just about any language feature ever. What i do not understand is the decision to not inherit default interface methods into classes. As @Jorenkv stated earlier this year (comment) this would be very helpful.

In fact i believe it is a necessity of the Traits concept. I quote the paper you mentioned:

Trait composition enjoys the flattening property. This property says that the semantics of a class defined using traits is exactly the same as that of a class constructed directly from all of the nonoverridden methods of the traits. So, if class A is defined using trait T, and T defines methods a and b, then the semantics of A is the same as it would be if a and b were defined directly in the class A. Naturally, if the glue code of A defines a method b directly, then this b would override the method b obtained from T.

I believe i understand why it would not be required for the use case this proposal is meant for (extending an interface without the need to change all implementing classes). I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem (i suppose there is one, i just don't see it)

I think Traits would be a very useful language feature, so i am looking forward to this. Of course it can be abused, but i guess this is true for just about any language feature ever. What i do not understand is the decision to not inherit default interface methods into classes. As @Jorenkv stated earlier this year (comment) this would be very helpful.

In fact i believe it is a necessity of the Traits concept. I quote the paper you mentioned:

Trait composition enjoys the flattening property. This property says that the semantics of a class defined using traits is exactly the same as that of a class constructed directly from all of the nonoverridden methods of the traits. So, if class A is defined using trait T, and T defines methods a and b, then the semantics of A is the same as it would be if a and b were defined directly in the class A. Naturally, if the glue code of A defines a method b directly, then this b would override the method b obtained from T.

I believe i understand why it would not be required for the use case this proposal is meant for (extending an interface without the need to change all implementing classes). I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem (i suppose there is one, i just don't see it)

@DavidArno

This comment has been minimized.

Show comment
Hide comment
@DavidArno

DavidArno Jan 4, 2018

I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem

If you have read all the comments here and still do not see why some of us do not like this feature, then you'll likely never see it. 😀

But the point is moot anyway. Despite all the voices against it and despite twice as many 👎's as 👍's, this feature is going to happen. There's therefore no point in any of us wasting more time on discussing its relative merits and disadvantages any further.

DavidArno commented Jan 4, 2018

I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem

If you have read all the comments here and still do not see why some of us do not like this feature, then you'll likely never see it. 😀

But the point is moot anyway. Despite all the voices against it and despite twice as many 👎's as 👍's, this feature is going to happen. There's therefore no point in any of us wasting more time on discussing its relative merits and disadvantages any further.

@felixhirt

This comment has been minimized.

Show comment
Hide comment
@felixhirt

felixhirt Jan 4, 2018

I might be wrong, but i think i see the reasons people dislike the feature:

  • Time might be used on other, more useful features
  • This proposal would introduce partial multi-inheritance (partial because its only logic, not state). Some people would likely prefer there be no multi-inheritance of logic or state, only, well, interfaces (what public members a class will expose), others would prefer the whole thing (including state)
  • This proposal changes the idea of an "interface", and its not really the same thing anymore (which i guess is why the authors of the paper called it a Trait and not an interface++)
  • This proposal adds the typical diamond of death problems to c# inheritance (which can be fixed pretty easy; most specific override rule)

Personally i have no problems with all of those points, but i understand anyone with a different opinion.
I was referring specifically to the detail about classes not inheriting default interface methods, not the whole proposition of there being default interface methods (I hope this makes sense). And i really just whish to understand why this decision was made, as opposed to having default interface methods AND having classes inherit the default interface methods of interfaces they implement. If i am not completely wrong here, that's how it works in java as well (not that this means it has to be done that way or anything)

I also understand your point about this discussion being pointless (i guessed that when i saw the issue was closed), i was just hoping that someone could give me some insight into the argumentation for this decision. Might very well be that i am in the wrong place for this here, but i don't know where else to ask.

felixhirt commented Jan 4, 2018

I might be wrong, but i think i see the reasons people dislike the feature:

  • Time might be used on other, more useful features
  • This proposal would introduce partial multi-inheritance (partial because its only logic, not state). Some people would likely prefer there be no multi-inheritance of logic or state, only, well, interfaces (what public members a class will expose), others would prefer the whole thing (including state)
  • This proposal changes the idea of an "interface", and its not really the same thing anymore (which i guess is why the authors of the paper called it a Trait and not an interface++)
  • This proposal adds the typical diamond of death problems to c# inheritance (which can be fixed pretty easy; most specific override rule)

Personally i have no problems with all of those points, but i understand anyone with a different opinion.
I was referring specifically to the detail about classes not inheriting default interface methods, not the whole proposition of there being default interface methods (I hope this makes sense). And i really just whish to understand why this decision was made, as opposed to having default interface methods AND having classes inherit the default interface methods of interfaces they implement. If i am not completely wrong here, that's how it works in java as well (not that this means it has to be done that way or anything)

I also understand your point about this discussion being pointless (i guessed that when i saw the issue was closed), i was just hoping that someone could give me some insight into the argumentation for this decision. Might very well be that i am in the wrong place for this here, but i don't know where else to ask.

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Jan 4, 2018

@felixhirt Your 3 points is somehow right. We just split in two side that we do care about multiple inheritance and interface principal so much and we gain too little just for enable retroactive extensible interface. While the other side has completely reverse opinion, they want extensible interface but never care about interface principle and diamond inheritance breaking

As for me who like interface principle, extending interface already possible with extension method if we have concrete logic. We should not do virtual extension with vague logic from the start. And if we want new api for something we should create new interface in the new version

And also, in my opinion, this is not the trait at all. This is just multiple inheritance in disguise. If we have actual Trait system it would be much more powerful

Not only time might be used on other, more useful features. It could be used on the feature that would actually fix all these problem altogether, which is the real trait system

Thaina commented Jan 4, 2018

@felixhirt Your 3 points is somehow right. We just split in two side that we do care about multiple inheritance and interface principal so much and we gain too little just for enable retroactive extensible interface. While the other side has completely reverse opinion, they want extensible interface but never care about interface principle and diamond inheritance breaking

As for me who like interface principle, extending interface already possible with extension method if we have concrete logic. We should not do virtual extension with vague logic from the start. And if we want new api for something we should create new interface in the new version

And also, in my opinion, this is not the trait at all. This is just multiple inheritance in disguise. If we have actual Trait system it would be much more powerful

Not only time might be used on other, more useful features. It could be used on the feature that would actually fix all these problem altogether, which is the real trait system

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Jan 4, 2018

Member

I believe i understand why it would not be required for the use case this proposal is meant for (extending an interface without the need to change all implementing classes). I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem (i suppose there is one, i just don't see it)

There are two reasons.

  1. The language already inherits nothing from an interface into a class, so this is consistent with the way things work today. If your application benefits from the inheritance, then use interfaces instead of classes, and use classes only for the concrete types that provide the state at the point of instantiation.
  2. One of the major points of the proposal is to permit extending the API of an interface without breaking existing users of that interface. Inheriting additional members into classes that implement the interface would affect the meaning of existing clients, so that would undermine one purpose of the feature.
Member

gafter commented Jan 4, 2018

I believe i understand why it would not be required for the use case this proposal is meant for (extending an interface without the need to change all implementing classes). I still hope that someone could provide an explanation as to why the inheritance of default interface methods would be a problem (i suppose there is one, i just don't see it)

There are two reasons.

  1. The language already inherits nothing from an interface into a class, so this is consistent with the way things work today. If your application benefits from the inheritance, then use interfaces instead of classes, and use classes only for the concrete types that provide the state at the point of instantiation.
  2. One of the major points of the proposal is to permit extending the API of an interface without breaking existing users of that interface. Inheriting additional members into classes that implement the interface would affect the meaning of existing clients, so that would undermine one purpose of the feature.
@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Jan 4, 2018

Member

@Thaina Please have a look at the definition of Traits in http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf and tell me how this differs from a "real" trait system.

Member

gafter commented Jan 4, 2018

@Thaina Please have a look at the definition of Traits in http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf and tell me how this differs from a "real" trait system.

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Jan 4, 2018

@gafter While I had read that paper, I may overlook something, but that paper was all about things that could be done with current interface

What I mean the actual trait is, sorry I was misuse the word, but I meant shape #164

Thaina commented Jan 4, 2018

@gafter While I had read that paper, I may overlook something, but that paper was all about things that could be done with current interface

What I mean the actual trait is, sorry I was misuse the word, but I meant shape #164

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Jan 4, 2018

Member

While I had read that paper, I may overlook something, but that paper was all about things that could be done with current interface

No, current interfaces do not have concrete methods.

What I mean the actual trait is, sorry I was misuse the word, but I meant shape #164

The two proposals are unrelated. In fact, I think they are complimentary. I would use the term "concepts" or "type classes" for #164 and its ilk.

Member

gafter commented Jan 4, 2018

While I had read that paper, I may overlook something, but that paper was all about things that could be done with current interface

No, current interfaces do not have concrete methods.

What I mean the actual trait is, sorry I was misuse the word, but I meant shape #164

The two proposals are unrelated. In fact, I think they are complimentary. I would use the term "concepts" or "type classes" for #164 and its ilk.

@agocke

This comment has been minimized.

Show comment
Hide comment
@agocke

agocke Jan 4, 2018

Contributor

@gafter

@Thaina Please have a look at the definition of Traits in http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf and tell me how this differs from a "real" trait system.

Paper:

Trait composition does not affect the semantics of a class: the meaning of the class
is the same as it would be if all of the methods obtained from the trait(s) were
defined directly in the class.

😄

Contributor

agocke commented Jan 4, 2018

@gafter

@Thaina Please have a look at the definition of Traits in http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf and tell me how this differs from a "real" trait system.

Paper:

Trait composition does not affect the semantics of a class: the meaning of the class
is the same as it would be if all of the methods obtained from the trait(s) were
defined directly in the class.

😄

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Jan 4, 2018

Member

@agocke See #288 (comment) . I don't think that was what @Thaina was complaining about. To get the semantics from the Traits paper, use interfaces for your "classes", and only use classes at the point of construction for the object that provides concrete state.

Member

gafter commented Jan 4, 2018

@agocke See #288 (comment) . I don't think that was what @Thaina was complaining about. To get the semantics from the Traits paper, use interfaces for your "classes", and only use classes at the point of construction for the object that provides concrete state.

@agocke

This comment has been minimized.

Show comment
Hide comment
@agocke

agocke Jan 4, 2018

Contributor

That would work, but I think we should consider if we want a simpler mechanism to bring members with default implementation into the class public API. I could see it being a relatively common use case.

Contributor

agocke commented Jan 4, 2018

That would work, but I think we should consider if we want a simpler mechanism to bring members with default implementation into the class public API. I could see it being a relatively common use case.

@felixhirt

This comment has been minimized.

Show comment
Hide comment
@felixhirt

felixhirt Jan 4, 2018

@gafter Thank you for the explanation, I think I see your points now. Consistency is important and i also understand your second argument, it makes sense to prevent classes from magically getting new methods.

If classes inherit default methods from interfaces, we would have the same problems again that we have now right? Correct me if I’m wrong, but a class would be broken after an interface is extended with a new default method if it also implements another interface which had a default method with the same signature already, because there would be no most specific override. So, we would have to modify the class -> back to field one.

Despite all of this, I don’t see how the trait system would be of much use like this (I understand you correctly right? You propose to have a super-interface implement several interfaces with default methods (traits), then write a class which implements this super-interface so you have something to construct, but work with references to the super-interface instead of the class, so you have access to all the default methods?). I agree with agocke that it would be very nice (and, in my opinion, much more useful from a Trait-perspective) to have some mechanism to extend a class API with default interface methods.

felixhirt commented Jan 4, 2018

@gafter Thank you for the explanation, I think I see your points now. Consistency is important and i also understand your second argument, it makes sense to prevent classes from magically getting new methods.

If classes inherit default methods from interfaces, we would have the same problems again that we have now right? Correct me if I’m wrong, but a class would be broken after an interface is extended with a new default method if it also implements another interface which had a default method with the same signature already, because there would be no most specific override. So, we would have to modify the class -> back to field one.

Despite all of this, I don’t see how the trait system would be of much use like this (I understand you correctly right? You propose to have a super-interface implement several interfaces with default methods (traits), then write a class which implements this super-interface so you have something to construct, but work with references to the super-interface instead of the class, so you have access to all the default methods?). I agree with agocke that it would be very nice (and, in my opinion, much more useful from a Trait-perspective) to have some mechanism to extend a class API with default interface methods.

@Thaina

This comment has been minimized.

Show comment
Hide comment
@Thaina

Thaina Jan 5, 2018

@gafter

if all of the methods obtained from the trait(s) were defined directly in the class.

I think this is the point that mean trait has no concrete method. But it is the class that implement it. Because class must defined it and so it must be implemented because it is in class, or at least defined as abstract. While default method means that was defined and implement in the interface without awareness of class itself at all

Thaina commented Jan 5, 2018

@gafter

if all of the methods obtained from the trait(s) were defined directly in the class.

I think this is the point that mean trait has no concrete method. But it is the class that implement it. Because class must defined it and so it must be implemented because it is in class, or at least defined as abstract. While default method means that was defined and implement in the interface without awareness of class itself at all

@Xenoprimate

This comment has been minimized.

Show comment
Hide comment
@Xenoprimate

Xenoprimate Mar 18, 2018

I agree with @felixhirt and others who've opined that this feature doesn't seem to be of much use with respect to traits. I don't see how this proposal adds anything to the language that we can't already acheive with other workarounds; mostly because you're not inheriting default methods.

I can already write something like this:

public interface IMyTrait {
	void SomeMethod();
}

public static class MyTraitDefaultImplProvider {
	public static void SomeMethod(IMyTrait @this) {
		Console.WriteLine($"Caller: {@this}");
	}
}

public class TraitImplementingClass : IMyTrait {
	public void SomeMethod() => MyTraitDefaultImplProvider.SomeMethod(this);
}

The proposed net result of being able to replace MyTraitDefaultImplProvider.SomeMethod(this) with IMyTrait.base.SomeMethod() from this context is not really any kind of useful addition, in my opinion. The only small boon I see is that default implementations are defined in the interface itself rather than in a 'friend' static class.

I actually really want traits in C# (in fact I'd even push for full mixins/MI, but that's for another discussion). However I feel like using this feature as it stands is not meaningfully useful (in the context of supporting traits). Furthermore, it just seems to be distracting from the debate around the other motivations.

Apologies if I'm making a point that's already been rebutted. There's a lot of comments on this page!

I agree with @felixhirt and others who've opined that this feature doesn't seem to be of much use with respect to traits. I don't see how this proposal adds anything to the language that we can't already acheive with other workarounds; mostly because you're not inheriting default methods.

I can already write something like this:

public interface IMyTrait {
	void SomeMethod();
}

public static class MyTraitDefaultImplProvider {
	public static void SomeMethod(IMyTrait @this) {
		Console.WriteLine($"Caller: {@this}");
	}
}

public class TraitImplementingClass : IMyTrait {
	public void SomeMethod() => MyTraitDefaultImplProvider.SomeMethod(this);
}

The proposed net result of being able to replace MyTraitDefaultImplProvider.SomeMethod(this) with IMyTrait.base.SomeMethod() from this context is not really any kind of useful addition, in my opinion. The only small boon I see is that default implementations are defined in the interface itself rather than in a 'friend' static class.

I actually really want traits in C# (in fact I'd even push for full mixins/MI, but that's for another discussion). However I feel like using this feature as it stands is not meaningfully useful (in the context of supporting traits). Furthermore, it just seems to be distracting from the debate around the other motivations.

Apologies if I'm making a point that's already been rebutted. There's a lot of comments on this page!

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 18, 2018

Member

@Xenoprimate this provides the effect of the language feature traits in interfaces - names from base interfaces are indeed inherited into derived interfaces. If you choose to program in a style in which you use traits instead of classes, all of your code (in interfaces) would have access to these inherited names. You'd only use a class at the leaves to instantiate a trait and provide state backing its properties; you would not program against the class types.

Member

gafter commented Mar 18, 2018

@Xenoprimate this provides the effect of the language feature traits in interfaces - names from base interfaces are indeed inherited into derived interfaces. If you choose to program in a style in which you use traits instead of classes, all of your code (in interfaces) would have access to these inherited names. You'd only use a class at the leaves to instantiate a trait and provide state backing its properties; you would not program against the class types.

@Xenoprimate

This comment has been minimized.

Show comment
Hide comment
@Xenoprimate

Xenoprimate Mar 18, 2018

@gafter Thank you for the swift response.

You say that we would program against the interface types and not the class types (which I agree makes sense, and is a good rule of thumb anyway), however I'm still not seeing much advantage here, compared to what we already have available to us today. Names are already inherited in interfaces, and as I demonstrated in my example above it's already possible to provide a 'default implementation' to defer to by way of a static helper class.

So is this just about encapsulation benefits (i.e. being able to inherit protected members); and/or just about not having to separate the default implementation from the interface?

Both are small advantages for sure, but in my opinion the killer advantage of traits is the automatic inheritance of functionality all the way down the chain (including the leaves); and without that I'm not sure I would consider this a proper traits implementation.

Xenoprimate commented Mar 18, 2018

@gafter Thank you for the swift response.

You say that we would program against the interface types and not the class types (which I agree makes sense, and is a good rule of thumb anyway), however I'm still not seeing much advantage here, compared to what we already have available to us today. Names are already inherited in interfaces, and as I demonstrated in my example above it's already possible to provide a 'default implementation' to defer to by way of a static helper class.

So is this just about encapsulation benefits (i.e. being able to inherit protected members); and/or just about not having to separate the default implementation from the interface?

Both are small advantages for sure, but in my opinion the killer advantage of traits is the automatic inheritance of functionality all the way down the chain (including the leaves); and without that I'm not sure I would consider this a proper traits implementation.

@gafter

This comment has been minimized.

Show comment
Hide comment
@gafter

gafter Mar 18, 2018

Member

it's already possible to provide a 'default implementation' to defer to by way of a static helper class

Not without active help from every class that implements the interface. Adding a new method would require changing every class that implements it! One of the points of this feature is that you can add to a base interface without changing its implementations.

There is no other way to get multiple inheritance of method implementations today.

in my opinion the killer advantage of traits is the automatic inheritance of functionality all the way down the chain (including the leaves)

If you are using this feature to program using traits, then your leaves are interfaces. Classes are present only as an implementation detail of creation and contain no interesting code other than state to implement properties of the interfaces. The only time you would mention a class would be to instantiate one of these interfaces, and you would immediately store the created value into an object of an interface type. Inherited names in those classes would be pointless because those classes have no interesting code in them to use those names.

In any case, we are providing a way of accessing members of implemented interfaces. The syntax may be something like Interfacename.base.M().

Member

gafter commented Mar 18, 2018

it's already possible to provide a 'default implementation' to defer to by way of a static helper class

Not without active help from every class that implements the interface. Adding a new method would require changing every class that implements it! One of the points of this feature is that you can add to a base interface without changing its implementations.

There is no other way to get multiple inheritance of method implementations today.

in my opinion the killer advantage of traits is the automatic inheritance of functionality all the way down the chain (including the leaves)

If you are using this feature to program using traits, then your leaves are interfaces. Classes are present only as an implementation detail of creation and contain no interesting code other than state to implement properties of the interfaces. The only time you would mention a class would be to instantiate one of these interfaces, and you would immediately store the created value into an object of an interface type. Inherited names in those classes would be pointless because those classes have no interesting code in them to use those names.

In any case, we are providing a way of accessing members of implemented interfaces. The syntax may be something like Interfacename.base.M().

@Xenoprimate

This comment has been minimized.

Show comment
Hide comment
@Xenoprimate

Xenoprimate Mar 18, 2018

Adding a new method would require changing every class that implements it! One of the points of this feature is that you can add to a base interface without changing its implementations.

That's fair enough. For my own use-cases I think I'll usually be controlling my own API and consumption of that API; but I can totally respect that that isn't the case for everyone, so yes I can definitely see how this is an improvement here.

If you are using this feature to program using traits, then your leaves are interfaces. Classes are present only as an implementation detail of creation and contain no interesting code other than state to implement properties of the interfaces. The only time you would mention a class would be to instantiate one of these interfaces, and you would immediately store the created value into an object of an interface type.

I think that's a fair ideal, but I'm not sure how well it will hold up in certain use-cases. Instead of speaking in abstract terms let me throw an example from my own world of game engine development. Let's say I want a few composable trait/mixin types to build in-game entity classes with (entities such as ArmourPowerup or RocketProjectile for example).

I might want to make an ISoundEmittingEntity and ILevelPlaceableEntity trait type, where an ArmourPowerup would emit a small ambient sound and be placeable in my game level (e.g. it would inherit from ISoundEmittingEntity and ILevelPlaceableEntity). The ISoundEmittingEntity interface would have some default methods for audible radius, volume, the sound effect loaded, looping controls, etc... And I'm sure you can imagine similar concepts for other entity traits.

The first problem then that I foresee is when the number of trait types is an order of magnitude or two less than the number of leaves. For example: Whereas there is a small amount of traits that any one entity could have (maybe 20 or so, like sound-emitting, placeable, physical, etc.), the number of actual entity types that my game might support could be in the hundreds. I personally don't really want to have to create leaf interfaces for every entity type I wish to create as well as a class concretion; and maintain parity between them at all times.

The alternative perhaps is some combination interfaces, like ISoundEmittingAndLevelPlaceableEntity; but the combinatorial explosion alone is prohibitive there. Or am I missing some important obvious step here in reducing the need for this?

On a minor note, as I'm sure you're aware in the world of game development performance is a critical feature; and forcing all leaves to be interfaces means forcing boxing of structs. In places where the traits are used as their trait type anyway that is unavoidable of course*; but in other places I'd rather use the struct as a leaf, and not a pair-interface. Nonetheless, it's really the first problem that is the 'major' one to me.

Anyway, that's just an example; and of course there are already existing entity systems written in C# that don't require this approach (and some may argue that components should be composable at runtime anyway); but I'd ask anyone not to get too hung up on the actual example but instead just focus on my overall point, which is that I believe requiring leaf interfaces will be frustrating when working with many leaves.

* Ignoring mitigation via sending parameters as generic types and allowing reification to write optimised implementations

Xenoprimate commented Mar 18, 2018

Adding a new method would require changing every class that implements it! One of the points of this feature is that you can add to a base interface without changing its implementations.

That's fair enough. For my own use-cases I think I'll usually be controlling my own API and consumption of that API; but I can totally respect that that isn't the case for everyone, so yes I can definitely see how this is an improvement here.

If you are using this feature to program using traits, then your leaves are interfaces. Classes are present only as an implementation detail of creation and contain no interesting code other than state to implement properties of the interfaces. The only time you would mention a class would be to instantiate one of these interfaces, and you would immediately store the created value into an object of an interface type.

I think that's a fair ideal, but I'm not sure how well it will hold up in certain use-cases. Instead of speaking in abstract terms let me throw an example from my own world of game engine development. Let's say I want a few composable trait/mixin types to build in-game entity classes with (entities such as ArmourPowerup or RocketProjectile for example).

I might want to make an ISoundEmittingEntity and ILevelPlaceableEntity trait type, where an ArmourPowerup would emit a small ambient sound and be placeable in my game level (e.g. it would inherit from ISoundEmittingEntity and ILevelPlaceableEntity). The ISoundEmittingEntity interface would have some default methods for audible radius, volume, the sound effect loaded, looping controls, etc... And I'm sure you can imagine similar concepts for other entity traits.

The first problem then that I foresee is when the number of trait types is an order of magnitude or two less than the number of leaves. For example: Whereas there is a small amount of traits that any one entity could have (maybe 20 or so, like sound-emitting, placeable, physical, etc.), the number of actual entity types that my game might support could be in the hundreds. I personally don't really want to have to create leaf interfaces for every entity type I wish to create as well as a class concretion; and maintain parity between them at all times.

The alternative perhaps is some combination interfaces, like ISoundEmittingAndLevelPlaceableEntity; but the combinatorial explosion alone is prohibitive there. Or am I missing some important obvious step here in reducing the need for this?

On a minor note, as I'm sure you're aware in the world of game development performance is a critical feature; and forcing all leaves to be interfaces means forcing boxing of structs. In places where the traits are used as their trait type anyway that is unavoidable of course*; but in other places I'd rather use the struct as a leaf, and not a pair-interface. Nonetheless, it's really the first problem that is the 'major' one to me.

Anyway, that's just an example; and of course there are already existing entity systems written in C# that don't require this approach (and some may argue that components should be composable at runtime anyway); but I'd ask anyone not to get too hung up on the actual example but instead just focus on my overall point, which is that I believe requiring leaf interfaces will be frustrating when working with many leaves.

* Ignoring mitigation via sending parameters as generic types and allowing reification to write optimised implementations

@rubenwe

This comment has been minimized.

Show comment
Hide comment
@rubenwe

rubenwe Mar 28, 2018

How will default implementations be handled if interfaces are marked as [ComVisible]?

Will they be visible/callable? How is the compatibility behaviour between versions of interfaces?

I'm not sure if this is a valid/problematic scenario. COM is not the most loved child. But it is still used in a lot of enterprise interop... As I have not seen any comment addressing the issue yet, I wanted to mention this, so it is on the radar.

rubenwe commented Mar 28, 2018

How will default implementations be handled if interfaces are marked as [ComVisible]?

Will they be visible/callable? How is the compatibility behaviour between versions of interfaces?

I'm not sure if this is a valid/problematic scenario. COM is not the most loved child. But it is still used in a lot of enterprise interop... As I have not seen any comment addressing the issue yet, I wanted to mention this, so it is on the radar.

@dsyme dsyme referenced this issue in fsharp/fslang-suggestions Jun 22, 2018

Open

Default interface methods #679

5 of 5 tasks complete
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment