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" (16.3, Core 3) #52

Open
3 of 5 tasks
gafter opened this issue Feb 10, 2017 · 763 comments
Open
3 of 5 tasks

Champion "default interface methods" (16.3, Core 3) #52

gafter opened this issue Feb 10, 2017 · 763 comments

Comments

@gafter
Copy link
Contributor

@gafter gafter commented Feb 10, 2017

Open issues are being tracked at #406

LDM history:

@scottdorman
Copy link
Contributor

@scottdorman scottdorman 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.)

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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?

Loading

@gafter
Copy link
Contributor Author

@gafter gafter 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.

Loading

@gafter
Copy link
Contributor Author

@gafter gafter 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.

Loading

@scottdorman
Copy link
Contributor

@scottdorman scottdorman 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.

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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.

Loading

@scottdorman
Copy link
Contributor

@scottdorman scottdorman 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.

Loading

@scottdorman
Copy link
Contributor

@scottdorman scottdorman 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.

Loading

@gulshan
Copy link

@gulshan gulshan 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?

Loading

@Thaina
Copy link

@Thaina Thaina 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

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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.

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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?

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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?

Loading

@Thaina
Copy link

@Thaina Thaina 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

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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".

Loading

@rolfbjarne
Copy link
Member

@rolfbjarne rolfbjarne 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; } }
}

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn commented Feb 20, 2017

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

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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.

Loading

@DavidArno
Copy link

@DavidArno DavidArno 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?

Loading

@Thaina
Copy link

@Thaina Thaina 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 { }

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@Thaina
Copy link

@Thaina Thaina 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

Loading

@eyalalonn
Copy link
Contributor

@eyalalonn eyalalonn 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.

Loading

@Tyrrrz
Copy link

@Tyrrrz Tyrrrz commented Sep 3, 2019

Unfortunately, yes it is. C# 8.0 is very much locked at this point, no more changes are being taken for initial release.

That's a shame, but I guess there was no harm in trying 😉

Loading

@BenjaBobs
Copy link

@BenjaBobs BenjaBobs commented Sep 17, 2019

I might not have understood it correctly, but shouldn't you be able to call default interface methods on concrete classes that don't override them?
With

public interface ITest
{
    void Default() => Console.WriteLine("With Default Impl");
    void NoDefault();
}

public class TestImplementer : ITest
{
	public void NoDefault() => Console.WriteLine("Without Default Impl");
}

I would expect to be able to do the following:

var tester = new TestImplementer();
tester.Default();
tester.NoDefault();

But in the following SharpLab example it won't compile.

Loading

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Sep 17, 2019

You need to cast the class to the interface first (((ITest)tester).Default(); will work). Classes don't inherit methods from the interface unfortunately.

Loading

@spydacarnage
Copy link

@spydacarnage spydacarnage commented Sep 17, 2019

Or explicity declare it:

ITest tester = new TestImplementer();
tester.Default();
tester.NoDefault();

Loading

@BenjaBobs
Copy link

@BenjaBobs BenjaBobs commented Sep 17, 2019

I suppose you could do

public static class TestExtensions
{
    public static void Default(this ITest iTest)
    {
        Console.WriteLine("Through extension");
        iTest.Default();
    }
}

But I'm sure it has it's own issues in terms of choosing the correct overload.

SharpLab

Loading

@junkbondtrader
Copy link

@junkbondtrader junkbondtrader commented Nov 14, 2019

Are there plans to support default interface methods in .Net Framework? (as opposed to .Net Core)

Loading

@jmarolf
Copy link
Member

@jmarolf jmarolf commented Nov 14, 2019

@junkbondtrader there are no plans to rev the CLR for framework to understand DIM IL optcodes

Loading

@junkbondtrader
Copy link

@junkbondtrader junkbondtrader commented Nov 15, 2019

Thank you @jmarolf

Loading

@IanKemp
Copy link

@IanKemp IanKemp commented Feb 4, 2020

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/default-interface-methods#static-and-private-methods

Open issue: If we support static methods, should we support (static) operators?

Did this make the cut for 8.0?

Loading

@gafter
Copy link
Contributor Author

@gafter gafter commented Feb 4, 2020

Yes, but not conversion operators

Loading

@IanKemp
Copy link

@IanKemp IanKemp commented Sep 14, 2020

Loading

@Thaina
Copy link

@Thaina Thaina commented Sep 18, 2020

One thing I wish we could be compromised

I just think if DIM would always required to be explicitly implement. It could avoid both unintentionally collision and diamond inheritance while keeping all other benefit except easiness of assume implementation

ps. revisiting because today I have read this blog

ps2. Have made #3906 for allow this behaviour as compiler flag

Loading

@tarsa
Copy link

@tarsa tarsa commented Sep 19, 2020

ps. revisiting because today I have read this blog

The crux of the article is:

So what happens when you implement an interface that has a default method, like IDocument? You have a choice. You can use the existing implementation of the Print() method (much as you might do when inheriting from a class), or you can roll your own. Classes that already implement IDocument keep working — but at a cost. There’s no guarantee the default implementation will make sense for them.

This is the central danger of default implementations — it allows careless or hurried programmers to retrofit code with something that may not be appropriate. If your code receives an object that uses the IDocument class, you have no way to tell if this object is using the default fallback implementation or its own tailor-made version. This make sense from an encapsulation point of view. But it also means that you could unwittingly use an object in a way that it doesn’t really support. Worst of all, the implementor gets no say on whether the implementation makes sense. They may not even know that the interface changed and their class has a new member! In the worst case scenario, you end up trading compile-time problems for wonky runtime behavior.

Probably this guy hasn't heard about the concept of typeclasses: https://en.wikipedia.org/wiki/Type_class They are an alternative to OOP-like inheritance (or rather old Java-like inheritance as OOP doesn't preclude multiple inheritance and in fact popular languages like C++ have multiple inheritance) and are akin to Java and C# interfaces (as they only define behaviour, not state). Type classes are present in Rust (traits), Swift (protocols), Scala (in Scala 2 they are emulated using implicits and OOP, but Scala 3 has more opinionated and purpose built syntax) and of course Haskell where they originated. Type classes can provide default implementations just like interfaces in Java 8+ or C# 8+. Are people complaining that Rust, Swift, Scala, Haskell, etc are broken because of default type class method implementations? Haven't heard that even once.

One of the primary use cases for default interface methods in Java were new functionalities for collections. Base interfaces got new default interface methods with correct but potentially inefficient implementation. Concrete subclasses were free to override these default implementations with more efficient ones. Since default interface methods in Java are virtual (like all other non-private non-static methods) this works as intended even when type of variable (holding reference to a collection) is not precise (i.e. it has type CollectionInterface instead of ConcreteCollection). In C# extension methods are used instead to enrich collections functionalities in backward compatible way. Since extension methods are static methods and resolved at compile time, you will always get the single implementation of extension method when you're coding to CollectionInterface, irrespective of real type of collection a variable is pointing to.

Loading

@aepot
Copy link

@aepot aepot commented Oct 11, 2020

dotnet --version
3.1.402

Based on StackOverflow question

Can someone explain the following implicit interface implementation with virtual keyword when deriving an abstrat class?

interface IA
{
    public string Method1() => "IA.Method1";
    public string Method2() => "IA.Method2";
}

class A : IA { }

abstract class B : A { }

class C : B
{
    public string Method1() => "C.Method1";
    public virtual string Method2() => "C.Method2";
}

This code

IA o = new C();
Console.WriteLine(o.Method1());
Console.WriteLine(o.Method2());

makes this output:

IA.Method1
C.Method2

If I remove abstract from B, I get this output:

IA.Method1
IA.Method2

Bug or feature? How must it work by design?

Loading

@HaloFour
Copy link
Contributor

@HaloFour HaloFour commented Oct 11, 2020

Might be a question for the runtime repo? Looks like the IL emitted by the compiler in both cases is:

callvirt instance string IA::Method2()

SharpLab

Loading

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Oct 12, 2020

Might be a question for the runtime repo? Looks like the IL emitted by the compiler in both cases is:

Nope, this is a Roslyn/C# question - Roslyn secretly turns public string Method1() => "C.Method1"; into public virtual string Method1() => "C.Method1"; when it believes that Method1 should implement the interface. This is changing based on whether B is abstract.

If a method is not virtual, it will not implement the interface from the runtime perspective.

Loading

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Oct 12, 2020

@MichalStrehovsky

This looks like a runtime bug to me. According to the following IL, which looks like I would expect it to look, type C doesn't claim to implement IA and none of the methods claim to explicitly implement methods from IA.

.class private auto ansi beforefieldinit C
    extends B
{
    // Methods
    .method public hidebysig 
        instance string Method1 () cil managed 
    {
        // Method begins at RVA 0x2070
        // Code size 6 (0x6)
        .maxstack 8

        IL_0000: ldstr "C.Method1"
        IL_0005: ret
    } // end of method C::Method1

    .method public hidebysig newslot virtual 
        instance string Method2 () cil managed 
    {
        // Method begins at RVA 0x2077
        // Code size 6 (0x6)
        .maxstack 8

        IL_0000: ldstr "C.Method2"
        IL_0005: ret
    } // end of method C::Method2

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x207e
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void B::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method C::.ctor

} // end of class C

Loading

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Oct 12, 2020

Method2 is a virtual method that matches by name and signature a method on IA, therefore it implements the interface method since the base class doesn't (per the old rules pre-default interface methods).

The runtime specifically implements default interface methods as a fallback. Only if the "old algorithm" cannot come up with an implementation, default interface methods are considered. In C, the "old algorithm" can come up with an answer.

If you believe this to be a bug, let's move this to the runtime repo. I don't own the CoreCLR type system anymore and people in charge of it should be aware.

Loading

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Oct 13, 2020

Method2 is a virtual method that matches by name and signature a method on IA, therefore it implements the interface method since the base class doesn't (per the old rules pre-default interface methods).

C doesn't claim to implement the interface, so no method without explicit method impl should be considered by runtime as a candidate for interface implementation. For that, either the type should claim to extend the interface (not its base), or the method should have an explicit method impl,

Loading

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Oct 13, 2020

For example, neither method in C2 implements the interface in this code https://sharplab.io/#v2:EYLgtghgzgLgpgJwDQxAgrgOyQExAagB8ABAJgAJiBGAdgFgAoAb0fLcqoDZKAWcgWQgBLTAAoAlK3YsG7OeQAqcWKMxwA7uQDCE8QG4p8xcpiqN20roOz2AX0PkH1bsT5KVASQCC5APaSbNhkjNmoAOmoATlFfMP44GAALXxwqKwc5cKiYuITknEtxfQd7BlLGEXgEADMIAGM4cm9GYPZiAGYOAAYBPJS08XIAXgA+cgAib1yk/vHrTM7qHviZgolhscmvafzSOcZyhjJyHxAmnyZyQ4hgWAR6mEoKACFyM4urxkZjrTfyZ5aTkWVGWfVS61GEy0O1m8zanQAbkIEDB0BAADbdXqrQobKEwgr7MpfI4ULwUM7NZhA8hIlFozFLbH5AZ48bkglUIkLWnI1EYrErXYQzYcoUpPbWa63GD3OqPY7PCknCiXQ4/ZVKwGBSjA0GrVmQ8ZaUic7nw3n0gVM8VrQZGk0EyUHRhAA==:

class A2 : IA
{
    public virtual string Method1() => "A2.Method1";
    public virtual string Method2() => "A2.Method2";
}

abstract class B2 : A2 { }

class C2 : B2
{
    public string Method1() => "C2.Method1";
    public virtual string Method2() => "C2.Method2";
}

Presence of DIM should make no difference here.

Loading

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Oct 13, 2020

If you believe this to be a bug, let's move this to the runtime repo. I don't own the CoreCLR type system anymore and people in charge of it should be aware.

dotnet/runtime#43340

Loading

@MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Oct 13, 2020

C doesn't claim to implement the interface, so no method without explicit method impl should be considered by runtime as a candidate for interface implementation

This is a valid thing to do in IL. C# won't compile it, but it works fine in IL:

interface IA
{
    void X();
}

abstract class A : IA
{
    // NOTE: no implementation for IA. C# would yell. IL or runtime don't care though.
}

class B : A
{
    public virtual void X()
    {
        Console.WriteLine("Hi");
    }
}

Loading

@AlekseyTs
Copy link
Contributor

@AlekseyTs AlekseyTs commented Oct 14, 2020

This is a valid thing to do in IL. C# won't compile it, but it works fine in IL

Interesting. Changing the runtime behavior would be a breaking change then.

Loading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet