Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Champion "default interface methods" #52

Open
gafter opened this issue Feb 10, 2017 · 712 comments

Comments

@gafter
Copy link
Member

commented Feb 10, 2017

Open issues are being tracked at #406

LDM history:

@scottdorman

This comment has been minimized.

Copy link
Contributor

commented Feb 10, 2017

Why are we limiting to only extend by allowing private and static methods? What about protected as well?

At Summit last year, @MadsTorgersen and I discussed how this could be used in the context of helping to properly implement IDisposable. From what I can remember of the discussion, and by way of a possible actual syntax, how about something like:

public interface IDisposable
{
        bool Disposed { get; protected set; }

        void Dispose() = 
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        };

        protected virtual void Dispose(bool disposing);
}

(Putting this here so it doesn't get lost. Pending the outcome of the question asked on dotnet/roslyn#17054 (comment) I can repost the comment to the right place.)

@DavidArno

This comment has been minimized.

Copy link

commented Feb 10, 2017

The spec says there are no alternatives, but there is one: the delegation pattern as suggested in Roslyn issues [Proposal] Deligation class and Proposal: C# interface delegation.

In my opinion at least, the delegation pattern has far more merit than default interface methods:

  1. It requires no CLR change (it would be a pure syntax sugar language feature),
  2. It operates via composition, rather than inheritance, allowing extension of even sealed classes and structs.
  3. It avoids introducing multiple inheritance to the C# language.

Should I submit a PR to update the spec to reflect this alternative, or will you do it, @gafter?

@gafter

This comment has been minimized.

Copy link
Member Author

commented Feb 10, 2017

@DavidArno Those "alternatives" do not address the principal motivation for the proposal, and are not, therefore, alternatives:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

@gafter

This comment has been minimized.

Copy link
Member Author

commented Feb 10, 2017

@scottdorman protected would not make sense, as no class has an interface as its base class. Moreover, a method invoked from a class receiver (even the implicit this) is never an interface method (because a class does not inherit methods from the interfaces it implements). We'd have to reimagine the meaning of protected in some way to make sense of that, and I'd rather not do that before the value of doing so is clear.

@scottdorman

This comment has been minimized.

Copy link
Contributor

commented Feb 11, 2017

@gafter I disagree that it doesn't make sense. Take the example of IDisposable. The full pattern is to include the protected virtual void Dispose(bool disposing) method, but since it's not part of the interface (because interfaces can't have protected members) it rarely gets implemented...which means that a lot of disposable classes end up being implemented the wrong way.

a class does not inherit methods from the interfaces it implements

This is true, but isn't this proposal sort of breaking that statement? We're basically saying "Implement this interface but you don't have to provide your own implementation for this interface method because the interface is already giving you one." You can then decide later on to implement the method yourself, in which case the default interface method is effectively ignored/overridden with the actual class implementation.

We'd have to reimagine the meaning of protected in some way

How so? This is simply saying that if you implement this interface there is a protected member that must be implemented. In that respect, it doesn't seem like it's any different than saying "The syntax for an interface is extended to permit private methods (the default access is public)", which is what we have in the proposal now.

@DavidArno

This comment has been minimized.

Copy link

commented Feb 12, 2017

Those "alternatives" do not address the principal motivation for the proposal, and are not, therefore, alternatives
@gafter,

In that case I'm downvoting this proposal as it:

  1. Requires CLR changes
  2. Operates via inheritance, rather than composition so cannot be applied to sealed classes and structs.
  3. It introduces multiple inheritance to the C# language.

Since the underlying aim of this proposal can be achieved using the non-alternative delegation pattern approach, which doesn't suffer from any of the above problems, this doesn't seem a good proposal.

@scottdorman

This comment has been minimized.

Copy link
Contributor

commented Feb 12, 2017

@DavidArno Given my earlier example of providing a default correct implementation of IDisposable.Dispose(), how would that be accomplished using your delegation pattern?

One of the goals of this proposal is:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

How would using your delegation pattern achieve that (in my earlier example) such that existing classes which already implement that member aren't broken but new classes can utilize the default implementation?

Just because a proposal requires CLR changes isn't a reason for it not to be done. If that were the case, there's a lot of language features we've gotten over the last several releases that wouldn't exist.

I don't see how this introduces multiple inheritance to the language. Yes, it provides a solution that other languages use multiple inheritance to solve, but it doesn't change the inheritance rules for C#. A class can still only inherit from a single base class and multiple interfaces.

@scottdorman

This comment has been minimized.

Copy link
Contributor

commented Feb 12, 2017

I think Proposal: C# interface delegation is useful, would reduce a lot of boiler plate code that has to be written, and would love to see it in the language; however, I don't think it addresses the ability to provide a default implementation for an interface method or property nor does it extend the interface syntax to allow private methods, static methods, "override" methods (and hopefully protected methods).

[Proposal] Deligation class looks to achieve a similar result, albeit with a much uglier and more confusing (in my opinion) syntax so it wouldn't be able to achieve the goals set out in this proposal either.

@gulshan

This comment has been minimized.

Copy link

commented Feb 13, 2017

I think interfaces can just be allowed to have an unnamed nested class, that implements the interface. The nested class then can provide all the proposed features. Thus the main interfacing portion will not be polluted.

Also, how does this go with proposed extension everything proposal?

@Thaina

This comment has been minimized.

Copy link

commented Feb 17, 2017

This actually has alternative with generic extension method

I agree with @DavidArno

The proposal is conflict in itself. The purpose is to create function with concrete logic for interface. If it is concrete logic then it should not be overridden and we should separate some logic to be function that class derived from interface should implemented instead

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 19, 2017

@DavidArno

It introduces multiple inheritance to the C# language.

Well, it's not like we can't mimic this today and I don't think that multiple inheritance is bad per se because as far as I understand things like the diamond problem is going to result an error but I can certainly see that it might open a can of worms. :)

I voted for this because I think it can come in handy at times but I really like to see more love to composition too.

@DavidArno

This comment has been minimized.

Copy link

commented Feb 20, 2017

@scottdorman,

Given my earlier example of providing a default correct implementation of IDisposable.Dispose(), how would that be accomplished using your delegation pattern?

Since @gafter seems opposed to having protected members in an interface, this could be a moot point.
However, rather than duck the issue with a technicality, I should answer your question. Something like the following could be used:

// built-in types
public interface IDisposable
{
    void Dispose();
}

public class Disposable : IDisposable
{
    private readonly Func<bool> _isDisposeNeeded;
    private readonly Action _dispose;

    public Disposable(Func<bool> isDisposeNeeded, Action dispose)
    {
        _isDisposeNeeded = isDisposeNeeded;
        _dispose = dispose;
    }

    public void Dispose()
    {
        if (_isDisposeNeeded())
        {
            _dispose();
            GC.SuppressFinalize(this);
        }         
    }
}

// my type
internal class MyDisposableType : delegate Disposable
{
    private bool _disposed;

    internal MyDisposableType() : delegate(() => !_disposed, PerformDispose) {}

    private void PerformDispose()
    {
        _disposed = true;
        // etc
    }
}

How would using your delegation pattern achieve that (in my earlier example) such that existing classes which already implement that member aren't broken but new classes can utilize the default implementation?

I wouldn't as that isn't necessary. As @Thaina says, this can already be achieved with extension methods.

@DavidArno

This comment has been minimized.

Copy link

commented Feb 20, 2017

@eyalsk,

If multiple inheritance is seen as no worse than single inheritance, then why not just allow full-blown multiple inheritance, rather than this "by the back door" partial approach?

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2017

@DavidArno Sure, I'd vote for it, haha... :)

I think that this solves a different problem though, even if we had MI in the language, it wouldn't solve the following problem:

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

I've been asked many times before what's the difference between abstract classes and interfaces and my guess is that many people don't know the difference or they do but end up making the wrong choice and choose interfaces over abstract classes so in order to help these people a "plaster" is introduced into the language and I'm in need of one because I'm not perfect, yet. 😉

Now, I completely agree with you on the following part:

It also enables many programming patterns that require multiple inheritance without the issues of multiply inherited state.

This doesn't make a lot of sense because the next request would be to enable this for classes and maybe more specifically abstract classes, I mean why not? people are going to "abuse" interfaces just to achieve this so to me it makes more sense to add MI into the language at the same time.

It's a new feature so they can always throw an error at compile-time in cases it isn't "safe" such as in the diamond problem.

@DavidArno

This comment has been minimized.

Copy link

commented Feb 20, 2017

@eyalsk,

Am I missing something re,

Virtual extension methods enable an API author to add methods to an interface in future versions without breaking source or binary compatibility with existing implementations of that interface.

For, as in my reply to @scottdorman, extension methods surely already provide this?

@Thaina

This comment has been minimized.

Copy link

commented Feb 20, 2017

I think abstract is outdated. Object in the real world actually isn't hierarchy. Instead it is a composite of multiple aspect. Abstract really is just a class packed along with interface that it should be separated and composed

It just extension method was introduced so late so we stuck with abstract class to contain concrete method. Abstract class that need to derived to implement NotImplementedException such as stream is one example. Some stream can seek, some can't, but it must all be stream so it need to implemented like that

Which I think it's wrong. It should be explicitly state at compile time what it could do

For the virtual method. I want to explain that most of the time virtual method has concrete logic. And request you to always include base.ThatMethod() somewhere in the overridden method
This also a disadvantage of inheritance. The implementor must know where should they put (or should not put) that base call so the logic still flowing as the api designer expect

Instead. Interface callback is more proper way to do thing like that. Because if logic really concrete and just want to allow some extension in the future. You should just made interface and extension method like a jigsaw that would be zigzag between concrete and extensible logic. So you can control where you think it should be really extended

static class Ext
{
    public static void DoSomething<T>(this T obj) where T : MyInterface
    {
         // startup
         obj.Before();

        if(obj.Predicate())
         // concrete logic a
        else // concrete logic b

         obj.After();
    }
}

public interface MyInterface
{
    void Before();
    bool Predicate{ get; }
    void After();
}

With this it more powerful to extend any class you want in any composite way you need than hierarchical that could be derived with only one linear inheritance

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2017

@DavidArno I think that you're looking at it from the wrong angle.

I don't think that the question should be whether extension methods CAN come in place of the "default interface methods" but whether they SHOULD and by that I mean sometimes there's functionality that NEEDS to be part of the interface but was missed from the design and it's too late to introduce new functionality or changes into the system so today people have two choices either to create a new interface or introduce a breaking change, both of these options might be bad, depends on your position.

Now, adding functionality through extension methods can solve the problem but sometimes it can be considered a hack to a faulty design, just as an extreme example extending a collection with a Add method through an extension method that by design needs to add stuff.

This is how I think about extension methods "if all you have is a hammer, everything looks like a nail".

@rolfbjarne

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2017

@DavidArno

Re:

Am I missing something re,

Virtual extension methods enable an API author to add methods to an interface in future versions >>without breaking source or binary compatibility with existing implementations of that interface.
For, as in my reply to @scottdorman, extension methods surely already provide this?

You can't provide a custom implementation of an extension method.

Example:

interface IA {
    public bool Something { get { return true; } }
}
class C : IA {
    public bool Something { get { return false; } }
}
@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2017

@Thaina

I think abstract is outdated. Object in the real world actually isn't hierarchy. Instead it is a composite of multiple aspect. Abstract really is just a class packed along with interface that it should be separated and composed

Stating that abstract classes are dated is a bold statement to make.

In object-oriented hierarchies don't come from the object themselves but from the way we perceive reality and think about objects so it's really about the way we (humans) tend to organize information.

Objects aren't hierarchical but the relations between them might be for example your own family tree.

It just extension method was introduced so late so we stuck with abstract class to contain concrete method. Abstract class that need to derived to implement NotImplementedException such as stream is one example. Some stream can seek, some can't, but it must all be stream so it need to implemented like that

I think that you refer to NotSupportedException and if anything you need to blame the design and not the tool which btw given the circumstances is a reasonable design.

Extension methods wouldn't help here.

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 20, 2017

@rolfbjarne Yes, well, you can "extend" the object and "pretend" that somehow you extended the type. :)

@DavidArno

This comment has been minimized.

Copy link

commented Feb 20, 2017

@rolfbjarne,

I think you can. I haven't checked the code below, but I think it would work:

public interface IA
{
    bool Something { get; }
}

public static class IAExtentions
{
    public static bool SomethingElse(this IA a) => true; 
}

class C : IA
{
    public bool Something => false;
    public bool SomethingElse() => false;
}

The SomethineElse in C will override the extension method. Or am I wrong?

Of course, if the rules on where extension methods can be defined were relaxed, the interface could be modified to the following, to avoid creating a new type just for the extension, and without breaking existing implementations of IA (exactly as this proposal was aiming for):

public interface IA
{
    bool Something { get; }
    public static bool SomethingElse(this IA a) => true; 
}

But that may be taken the relaxation of the rules too far.

@DavidArno

This comment has been minimized.

Copy link

commented Feb 20, 2017

@eyalsk,

I may be wandering into the realms of "devils advocate" here, but taking the definition of the open/closed principle as ""software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification" (taken from Wikipedia), then surely using extension methods to extend an existing interface is exactly what we should do, rather than being a hack?

@Thaina

This comment has been minimized.

Copy link

commented Feb 21, 2017

@eyalsk

In object-oriented hierarchies don't come from the object themselves but from the way we perceive reality and think about objects so it's really about the way we (humans) tend to organize information.

It is just one perspective that we use to perceived a world. And which I was state, it outdated

Today there are many approach try to workaround to do composition object. Because that what we really use to construct object in factory. object is composed. Many times some object derived from something but lose some functionality. It does not hierarchy but mutation

Objects aren't hierarchical but the relations between them might be for example your own family tree.

My family tree is really great example. I may have something similar to my parents but I have many aspect lose from each of them. Object-oriented model hierarchy is wrong way to interpret real world. It can only has one parent and it can only expand

If I would represent my family tree I would use tree node, not object inheritance. I think relation is data. Not an object structure

Object that we made in programming also don't like a natural object. It mostly like artificial object that, as I said, construct from factory, which almost composed of various interface and module

if anything you need to blame the design and not the tool which btw given the circumstances is a reasonable design. Extension methods wouldn't help here.

That's why I don't said it bad. I just said it outdated. Because in the old day, that circumstance make an object-oriented hierarchy perspective most reasonable. But now we have better perspective and better tool we can revise that it have another approach which might be better

In my opinion Stream could be just interface, and might be many interface, IReadableStream, IWriteableStream, ISeekableStream. And all concrete logic of stream can ported to extension method of each respective interface. Instead of class Stream we would have IStream to wrap other object

interface IStream : IReadableStream,IWriteableStream,ISeekableStream{}

class MemoryStream : IStream { }

class HttpRequestStream : IWriteableStream { }

class HttpResponseStream : IReadableStream { }
@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 21, 2017

@DavidArno

software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification

There are many interpretation to this principle, some even contradicts one another so let's stick to the wiki one: "entity can allow its behaviour to be extended without modifying its source code."

Now, you're right you can "extend" the object with new behaviour without ever touching the original class but it's important to note that you're not actually extending anything but adding new behaviour to existing objects, after all, extension methods are just static methods in disguises and we're basically passing objects to them so in some cases where things are dependent on the type it won't work, so here are two scenarios where it won't work:

  1. Let's say you're building a plugin system and you have two assemblies PluginEngine and PluginInterface, the former basically binds between the various components and creates the required objects that are used to extends the system whereas the latter is used by both the engine and plugin authors to implement the plugin.

    Let's say that you shipped a new version of the software and you want to add missing functionality to one of the interfaces in PluginInterface so you add it through extension methods but it wouldn't quite work because the engine won't pick them up so now, you need to come up with your own mechanism to workaround this limitation so you will need to modify the engine so it would understand that default methods exist in some other static classes.

    Let's say I'm a plugin author and I want to override one of the extension methods but I can't really do that so now the engine needs to be modified yet again to somehow allow plugin authors to override your default methods.

    With the default interface methods approach you will only need to update the PluginInterface and I may or may not update my plugin, ever and things will continue to work.

    Now, obviously, when the software is in development or fairly new you can change and break things with minimal costs but the moment it gained traction and you need to think about backward compatibility and still extend the software you can't introduce a breaking change.

    C# is not a new language, plugin engines were written with it and if the software has many plugins you wouldn't want to break anyone but you wouldn't want to be in a position that you can't extend the system either so this is just another approach to give you that option.

    tl;dr; may not work with reflection when a component tries to create objects solely based on interfaces and their implementations like in the case of plugin engines, IoC frameworks, etc..

  2. The using statement expects types to implement the IDisposable interface; adding a Dispose method through an extension method won't work.

Beyond these two technical points there are also authors of frameworks and libraries that want to deliver new functionality to their existing types without breaking anyone, just a matter of decision and with this proposal their consumers can override the default methods whenever they want at their own time.

then surely using extension methods to extend an existing interface is exactly what we should do, rather than being a hack?

Again, it really depends, if by design I have an interface that is missing core functionality then using extension methods to do that would be a "hack" because I can't touch the interface but if default interface methods would exist then I could add the functionality to the interface itself.

I guess that you're more pragmatic in the sense that it doesn't bother you that core functionality isn't part of the interface as long as you have access to it whereas in my mind extension methods are used to provide additional functionality on top of existing one, I'd usually put them in separate namespace and/or different assembly.

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 21, 2017

@Thaina

If I would represent my family tree I would use tree node, not object inheritance.

I didn't say you would use inheritance to implement it I just said that hierarchies exist in the relations that exist between objects but your nodes can actually be hierarchical for example FatherNode and MotherNode might have their gender fixed to male and female respectively where both of them may derive from PersonNode.

In my opinion Stream could be just interface, and might be many interface, IReadableStream, IWriteableStream, ISeekableStream. And all concrete logic of stream can ported to extension method of each respective interface. Instead of class Stream we would have IStream to wrap other object

You're right, it is an opinion and a choice but you can read about why they designed it the way they did here.

@Thaina

This comment has been minimized.

Copy link

commented Feb 21, 2017

@eyalsk

extension methods are just static methods in disguises and we're basically passing objects to them

I would like to argue that this is the underlying mechanism of class and object instance method anyway. Extension method just expose that underlying mechanism to the surface

This is the perspective that was actually blown my mind. Instance method is just actually function that always pass instance object as this. And it just that every object know it's own type. So it go referencing function from it type

How instance method actually differ from extension method?

And I think it is the core concept as golang. You can write new method for any class in golang like extension method. It very powerful and it actually work like charm

@eyalsk

This comment has been minimized.

Copy link
Contributor

commented Feb 21, 2017

@Thaina

How instance method actually differ from extension method?

They differ in the rules that are applied to them for example instance methods can be virtual and as such calls to them are polymorphic whereas static methods cannot be made virtual and aren't subject to polymorphism.

Now, you copied only part of the sentence but my point above was that when things are dependent on the type then extension methods will fail to work in some circumstances because they operate on an instance of the type and aren't part of the type itself.

Just because an instance of the type is passed to an instance method at run-time doesn't mean that when you pass an object to a static method it makes it the same thing.

@chamons

This comment has been minimized.

Copy link

commented Oct 16, 2018

@HaloFour I understand that (though the compile error could be better here as well) but Foo is being explicit here in that it is changing visibility.

This feature lets interfaces do such "fun" tricks, which I know is the whole point, but far away from the type in question (maybe a different library).

Let me amend my previous statement of my broken expectations - "I expect every instance of a class that implements an interface to be callable for every method listed in the interface unless explicitly spelled out by the derived class."

To back off though, I'm not trying to get into a language war here, I'm sure that csc-dim has implemented the speced behavior here correctly. I just wanted to provide feedback that it feels really weird to have things listed in interfaces that you can't call on types that implement those interfaces without casting.

@jnm2

This comment has been minimized.

Copy link
Contributor

commented Oct 16, 2018

Let me amend my previous statement of my broken expectations - "I expect every instance of a class that implements an interface to be callable for every method listed in the interface unless explicitly spelled out by the derived class."

A more accurate mental model goes the other direction. You should only expect classes to contain members callable from the outside that are explicitly spelled out to be public.

DIM sounds like it's having a wholesome effect nudging expectations closer to the C# paradigm.

@chamons

This comment has been minimized.

Copy link

commented Oct 16, 2018

That's fair - though until now, every item on an interface was implicitly public. DIM changes that.

@jnm2

This comment has been minimized.

Copy link
Contributor

commented Oct 16, 2018

That's fair - though until now, every item on an interface was implicitly public. DIM changes that.

Sure, but that was an exception to the rule which only applies when using interface-typed expressions. Your focus was on class-typed expressions.

@gulshan

This comment has been minimized.

Copy link

commented Oct 16, 2018

This feature just should not be called "Traits".

@jnm2

This comment has been minimized.

Copy link
Contributor

commented Oct 16, 2018

Is there any news about the plan to move ahead with this as a .NET Core-only feature (fsharp/fslang-suggestions#679 (comment), #52 (comment))?

The purpose of default interface methods as stated in the proposal is to enable certain evolution of interfaces without breaking changes. This affects two groups: the .NET Core base class library team, and community library authors.

Let's start with library authors. As mentioned earlier, libraries that target (or plan to ever target) .NET Standard, .NET Framework, UWP or other platforms would not be able to use default interface methods at all. This inability to use default interface methods would even apply to ASP.NET, since it targets platforms other than strictly .NET Core.

Look at the very first point in Microsoft's new Open-source library guidance set of documentation:

Aspects of high-quality open-source .NET libraries:

✔ Inclusive - Good .NET libraries strive to support many platforms and applications.

As called out here, a library that uses default interface methods fails to be a high-quality open-source library.
In order to resolve this conflict, default interface methods must work many platforms. Until then, the .NET Core target of a library can't declare default interface methods because that would result in a fundamentally different API from its other target platforms. That would also be a quality issue.

So much for community library authors. The remaining party, the .NET Core BCL team, could certainly put this to good use and I'd be happy for them to do so. However, my hopes are still up that this is something that will eventually benefit community libraries, too.

@orthoxerox

This comment has been minimized.

Copy link

commented Oct 17, 2018

@chamons

A class that implements some interface is not a derived class of that interface. Java makes it more clear with two separate keywords where C# uses a colon for both relationships.

@markusschaber

This comment has been minimized.

Copy link

commented Oct 17, 2018

I think it would be really bad if this feature is .NET Core only - it will hurt adoption for quite some time.
In my eyes, it's a must that all language features get into the next versions of the major platforms / platform implementations, that means, it should get into .NET Standard eventually, and be implemented by classic .NET, Mono, Xamarin, etc.

(I think I could live without classic ".NET Framework" if Microsofts policy is to replace it with .NET Core - but this requires porting some other features apart from UI, like COM interop, which are neither there yet nor planned, AFAICS.

@scottdorman

This comment has been minimized.

Copy link
Contributor

commented Oct 17, 2018

I agree with both @jnm2 and @markusschaber, if this becomes a .NET Core only feature, it's going to be really bad for adoption and library authors aren't going to use it because it will either limit the reach of their library or create a situation where there is different functionality (and potentially even different public API surfaces) between versions of that library.

@t00

This comment has been minimized.

Copy link

commented Oct 21, 2018

I quite oppose this feature not just because CLR change needed but also because interfaces would change their original purpose of defining contract. Default implementation can be done the usual way but as previously mentioned by @DavidArno usage can by much simplified by adding interface delegation, which also handles many more use cases dotnet/roslyn#13952 i.e. multiple inheritance with potentially dynamically changed implementations as effectively mixins will be supported, no need to change CLR at least for now as call optimisation might require it, simple syntax change, no need to worry about access and most of all it will make composition over inheritance much more natural in C#.

@HaloFour

This comment has been minimized.

Copy link
Contributor

commented Oct 21, 2018

@t00

That purpose remains. The contract exists between the interface and the consumer, and the members defined in that interface are all still guaranteed to be invokable by the consumer.

Interface delegation does not solve the primary motivation of wanting to evolve existing interfaces without breaking existing code that depends on those interfaces.

@t00

This comment has been minimized.

Copy link

commented Oct 21, 2018

@HaloFour

When default implementation is added, contract might change any time this implementation is change as the behaviour will change.

It also adds additional complexity to CLR by changing the meaning of Type attributes i.e. iirc IsInterface or even worse adding additional flag.

For the minimal gain of having some methods implemented in the interface instead of using an extension method, which is optional thus not forced to be included, there is too much implementation uncertainty, all just about syntactic sugar.

There are much more pressing feature requests, one of which might be the aforementioned interface delegation.

@HaloFour

This comment has been minimized.

Copy link
Contributor

commented Oct 21, 2018

@t00

When default implementation is added, contract might change any time this implementation is change as the behaviour will change.

That is the point; the contract may gain new members with new behaviors without breaking all existing consumers or implementers. But the contract entered into by those existing consumers has not changed.

It also adds additional complexity to CLR by changing the meaning of Type attributes i.e. iirc IsInterface or even worse adding additional flag.

Nothing changes there. An implementer still implements that interface, even if it doesn't implement the non-required members.

For the minimal gain of having some methods implemented in the interface instead of using an extension method, which is optional thus not forced to be included, there is too much implementation uncertainty, all just about syntactic sugar.

Extension methods cannot be used to evolve an existing interface. They are not capable of virtual dispatch or specialization by implementers. This is the only method that has been identified so far that can actually meet the primary motivation, which is adding new members to an existing interface without causing breaking changes. This is also a method proven to work in other languages like Java, and is a common feature of functional languages.

There are much more pressing feature requests, one of which might be the aforementioned interface delegation.

Interface delegation is just syntactic sugar and can be accomplished through code generation. As for "more pressing", that's a question of opinion, and the language team seems to disagree with you.

@t00

This comment has been minimized.

Copy link

commented Oct 21, 2018

@HaloFour

Thank you for a thorough description, I stand corrected regarding the feature implemented like described being useful when extending interfaces without forcing implementations on all consumers when added specialization opportunity is required.

On further note I hope the language team will see beyond the polymorphic opportunities of improved interfaces and allow more freedom by also adding interface delegation which should move C# towards composition and extend features by adding new specialized interfaces instead.

I must agree both features are a kind of syntactic sugar, in the end C# is already far beyond a point of being Turing-complete.

@Alumniminium

This comment was marked as disruptive content.

Copy link

commented Jan 12, 2019

DIM are a clusterfuck.

It makes me really fucking anxious about what you will throw in in the 9th language version...

@gafter gafter added this to 8.0 Candidate in Language Version Planning Mar 6, 2019

@adamstyl

This comment has been minimized.

Copy link

commented Mar 21, 2019

I have downvoted for 2 reasons:

  • I write java on daily basis and I've seen the feature of default interfaces being abused. "Let's put 20 methods in our interface and provide a default empty behavior for all because we don't want to redesign and split in 3-4-5-N interfaces".
  • This is no "traits" implementation! I'm sorry Mr @gafter I respect you for the work you did for Java, but I disagree on this one. When I hear "traits" I think of the traits implemented in Scala:
abstract class A {
  val message: String
}
class B extends A {
  val message = "I'm an instance of class B"
}
trait C extends A {
  def loudMessage = message.toUpperCase()
}
class D extends B with C

val d = new D
println(d.message)  // I'm an instance of class B
println(d.loudMessage)  // I'M AN INSTANCE OF CLASS B

In the above snippet taken from Scala tour website you see that the C trait has access to members of class A. One could argue that in C# class A and interface C could both extend the same interface. But as I see it's not the same and for sure "default methods != traits"

Couldn't binary compatibility be ensured on the level of CLR/JIT/whatever? I replace a foo.dll version 2.1 with a foo.dll version 2.5 that added a method in an interface. Couldn't the CLR detect and automatically "fix" the problem by adding the missing implementation? I don't know the internals of CLR, what I say might be stupid. Just an idea.

@bondsbw

This comment has been minimized.

Copy link

commented Mar 21, 2019

@adamstyl What implementation would the CLR provide? The point of DIM is to provide a useful default implementation.

@adamstyl

This comment has been minimized.

Copy link

commented Mar 21, 2019

@bondsbw when people say "this will provide binary compatibility" then I assume empty body for void methods and return default(T) where T is the return type for non void methods. My 2 cents. Though I'm more eager to see real traits in C#. Regardless of the default methods inclusion or not.

@thomaslevesque

This comment has been minimized.

Copy link

commented Mar 21, 2019

when people say "this will provide binary compatibility" then I assume empty body for void methods and return default(T) where T is the return type for non void methods

Your assumption is incorrect. The default implementation is provided by the interface's author; it's not a "dummy" automatically generated implementation, it's a real, useful one, implemented by hand.

@tarsa

This comment has been minimized.

Copy link

commented Mar 24, 2019

@adamstyl
I do not think that sticking to any definition of trait is advisable in any way. I rather see trait keyword to be abused. For example traits exists in both Scala and Rust, but they mean completely different things. Traits in Scala form an inheritance hierarchy (like interfaces in Java take part in inheritance hierarchy), but Rust traits are instead a limited form of type classes ( https://en.wikipedia.org/wiki/Type_class ).

Inheritance based polymorphism requires that all supertypes are listed in declaration part of the type, e.g.: class X extends Parent Y with TraitZ with TraitW. Type class is like an interface that can be implemented next to target type (e.g. instance of typeclass Ordering for type List can be defined next to type List), but they can be also implemented next to typeclass (e.g. instance of typeclass Ordering for type List can be defined next to type Ordering). This means that you can implement a typeclass you control for a type you can't edit. This is not possible in inheritance based polymporphism - you can't add your interface to existing class you can't edit. Note that Scala supports type classes too (using implicit parameters or context bounds), but that's out of scope.

Scala also have a sometimes used feature called class linearization, that applies to all supertypes, i.e. classes and traits. You can refer to a previous item in linearization chain like here:

class Four { def x = 4 }
trait Doubling extends Four { override def x = 2 * super.x }
trait PlusOne extends Four { override def x = 1 + super.x }

println((new Four with Doubling with PlusOne).x)
println((new Four with PlusOne with Doubling).x)

Which prints 9 and 10 respectively. This is not supported directly in Java and probably it's not worth implementing directly in C#.

Small summary of keyword confusion:

  • Scala's class means the same as Java's class
  • Scala's trait is something in between Java's class and Java's interface
  • Haskell's class means a type class, which is something completely different than Java's class
  • Rust's trait is a limited form of Haskell's type class, but it's still completely different than Java's class
@gafter

This comment has been minimized.

Copy link
Member Author

commented Mar 24, 2019

By "traits", I only meant to refer to the concept as defined here: https://en.wikipedia.org/wiki/Trait_(computer_programming) and here: http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf. That should have been super clear from https://github.com/dotnet/csharplang/blob/master/proposals/default-interface-methods.md. It doesn't really matter what other languages mean by traits. We're not going to add a trait keyword to C#. Even though what we're proposing for this language feature is almost precisely what is defined there (with two differences: interface members are not inherited into classes, and there is no mechanism to "remove" an inherited member in a derived interface), I only mentioned "traits" to refer to that concept. Given how many other programming languages use the term for different concepts, perhaps it was more confusing than helpful. Given that, I'll remove the word from the OP above.

@Joe4evr

This comment has been minimized.

Copy link

commented Jun 11, 2019

@jcouv You changed the text of the link, but not the link itself. 😉

@jcouv

This comment has been minimized.

Copy link
Member

commented Jun 11, 2019

@Joe4evr Good catch. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.