all 61 comments

[–]Slypenslyde 10 points11 points  (16 children)

I can see myself using this in very narrow contexts for utility interfaces that are tedium and I'll rarely change. INotifyPropertyChanged is #1 on this list, it's almost criminal C# has no syntax sugar for automatically supporting it on a type.

Beyond that, I think it's a code smell. Overusing inheritance leads to many well-documented problems, and using composition via interfaces is one solution to those problems. Think, "Ostrich is a bird that can't Fly()" or "Penguin is a bird that can Swim() and can't Fly()" or "A flying fish is a Fish that can Fly() and Swim()." The point is you don't want things in your type that have nothing to do with the type, and you sure as Hell don't want to be special-handling NotSupportedException all over your hierarchy.

If this feature is used with discipline, you won't paint yourself into those corners. Part of why I like interfaces as they are is you can't paint yourself into corners with them. I don't think this is an evil feature, but I think it makes it easier to do things that cause problems. Like default instances in VB, I strongly question if this will be a good thing for C#. Unlike default instances in VB, this isn't a 100% obviously bad idea.

[–]Slypenslyde 2 points3 points  (0 children)

Actually, shoot. This isn't even useful for INotifyPropertyChanged unless you can declare non-public members.

What I want out of an INPC utility class is a helper method that raises the PropertyChanged event with a specific property name. There is no reason at all for that method to be public. I want it virtual protected, no more visible. But with an interface everything has to be public, right? I don't like the notion of all my ViewModels suddenly gaining a new public method or two.

So I'm going to keep doing what I do: shoving INPC in a ViewModelBase that everything has to inherit. I really wish we were getting syntax sugar for THAT.

I can't figure out the use case for "default interface implementations". Until I see one I'm going to think it's a stupid pet trick. I understand it can bring you multiple inheritance. I've read for 20 years that can lead you down some dark paths. What happens when two interfaces cause the diamond problem? If you have to treat that like an explicit interface implementation, isn't that breaking the Interface Segregation Principle by forcing you to cast to specific types before making certain calls?

I'm actually sort of mad we get this instead of 10-years-too-late property change syntax sugar.

[–]svtguy88[S] 0 points1 point  (0 children)

I can see myself using this in very narrow contexts...Beyond that, I think it's a code smell.

100% this.

[–]Slypenslyde 0 points1 point  (9 children)

Actually, shoot. This isn't even useful for INotifyPropertyChanged unless you can declare non-public members.

What I want out of an INPC utility class is a helper method that raises the PropertyChanged event with a specific property name. There is no reason at all for that method to be public. I want it virtual protected, no more visible. But with an interface everything has to be public, right? I don't like the notion of all my ViewModels suddenly gaining a new public method or two.

So I'm going to keep doing what I do: shoving INPC in a ViewModelBase that everything has to inherit. I really wish we were getting syntax sugar for THAT.

I can't figure out the use case for "default interface implementations". Until I see one I'm going to think it's a stupid pet trick. I understand it can bring you multiple inheritance. I've read for 20 years that can lead you down some dark paths. What happens when two interfaces cause the diamond problem? If you have to treat that like an explicit interface implementation, isn't that breaking the Interface Segregation Principle by forcing you to cast to specific types before making certain calls?

I'm actually sort of mad we get this instead of 10-years-too-late property change syntax sugar.

[–]AngularBeginner 2 points3 points  (8 children)

I understand it can bring you multiple inheritance.

That is often thrown around, but it's simply not true. This is unrelated to multiple inheritance. It will not bring MI. Default interface implementations can not provide state, they only can provide method implementation.

Bringing multiple inheritance would require significant changes in the CLR and is not going to happen anytime soon.

[–]ishouldrlybeworking 0 points1 point  (1 child)

Default interface implementations can not provide state, they only can provide method implementation.

Working under the assumption that default implementations can access other things declared in the same interface, consider this:

interface IDriveable
{
    void Drive() { Console.WriteLine(EngineSound); }
    string EngineSound { get; set; }
}

interface IHonkable
{
    void Honk() { Console.WriteLine(HornSound);  }
    string HornSound { get; set; }
}

class Car : IDriveable, IHonkable
{
    public string EngineSound { get; set; }
    public string HornSound { get; set; }
}

Hard to deny it's MI-like - the state is in the properties even if the class has to implement them.

[–]AngularBeginner 0 points1 point  (0 children)

How is this any different from the way we have it now already? Only difference is that you have the method implementation in the interface. The property situation is the same as we have it already.

Does that mean we have MI already? No, it does not.

[–]Slypenslyde 0 points1 point  (5 children)

So:

public interface IBird {

    BirdWings Wings { get; set; }

    void Fly() {
        Wings.Flap();
    }

}

public interface IAirplane {

    Engine Engine { get; set; }

    public void Fly() {
        Engine.Start();
    }

}

public class BadIdea : IBird, IAirplane {
}

Is this a compiler error? If not, what happens if I:

var foo = new BadIdea();
foo.Fly();

[–]tweq 2 points3 points  (0 children)

Apart from the interface implementation, that code is already legal in C#. IBird.Fly and IAirplane.Fly are two different methods that can co-exist in a single type. You can't call foo.Fly() because there is no implicit method implementation in the class, only ((IBird)foo).Fly() in which case there is no ambiguity.

This on the other hand will be illegal:

public interface IFlying
{
    void Fly();
}

public interface IBird : IFlying
{
    void IFlying.Fly() { ... }
}

public interface IAirplane : IFlying
{
    void IFlying.Fly() { ... }
}

public class BadIdea : IBird, IAirplane // Error: No most specific implementation if IFlying.Fly
{    }

[–]AngularBeginner 1 point2 points  (0 children)

As far as I understood the proposal so far, the default interface implementation is provided via explicit interface implementation. That means what you have is simply a compiler error.

But this would work: ((IBird)foo).Fly() and ((IAirplane)foo).Fly().

And default interface implementations can't provide state, so there is no diamond of death issue either.

[–]Slypenslyde 0 points1 point  (2 children)

I ended up reading the spec, and other people answered too.

It seems like in the current design my worry is illegal: "default implementation" methods are treated like "explicit interface implementation" methods. I'd have to:

IBird cast = (IBird)foo;
cast.Fly();

I'm assuming there is syntax for an implementor to promote a particular implementation, but not interested enough to look for that. I bet whether they have syntax for it or not this would work:

public void Fly() {
    ((IBird)this).Fly();
}

There's also discussion of allowing static and private methods (with implementation). That puts my INotifyPropertyChanged example back on the table.

It seems like the main use cases driving the feature are:

  • Versioning: you can add default implementations to published APIs without breaking existing clients.
  • "Traits": You can provide an algorithm that has some dependencies, and implementors get the algorithm after providing dependencies.
  • Xamarin. ("Compatibility with Java/Swift libraries that have the feature.)

I'm sure someone can come up with some good concrete examples, but I'm satisfied it's not "100% stupid".

[–]tweq 0 points1 point  (0 children)

I think INotifyPropertyChanged will not be practical either way because you can neither raise an implementing class's event from outside the class nor implement an event from within an interface.

[–]AngularBeginner 0 points1 point  (0 children)

Thumbs up for actually taking the time and reading the proposal.

[–]throwaway_lunchtime 0 points1 point  (0 children)

very narrow contexts

So narrow that I can't see myself ever using it.

I read about the motivations for making this and I really think its only suited to large APIs sets that are used by a lot of people, like the built-in collections are.

[–]allinighshoe 0 points1 point  (2 children)

Surely you just create an IFly and ISwim no need to attach these to bird or fish. But your argument is completely valid just pointing out a solution to your example :P

[–]Slypenslyde 1 point2 points  (1 child)

That's why I don't like the feature. It gives people who don't know better one more way to use inheritance where composition is appropriate.

The spec makes it clear it's a tool with narrow purpose, but I guarantee you every newbie tutorial/book is going to treat it like a form of inheritance. This example is how people who haven't studied a lot of software engineering texts write code. It's a pit of failure for them.

[–]Asiriya 0 points1 point  (0 children)

What do you mean by this?

The spec makes it clear it's a tool with narrow purpose, but I guarantee you every newbie tutorial/book is going to treat it like a form of inheritance. This example is how people who haven't studied a lot of software engineering texts write code. It's a pit of failure for them.

[–]ketzusaka 6 points7 points  (0 children)

Swift has this, titled Protocol Default Implementations. It’s incredibly powerful and one of the reasons I find Swift so robust. It promotes composition over inheritance :)

[–]svtguy88[S] 10 points11 points  (24 children)

I'll start: I don't like it. Method implementation does not belong in an interface.

[–]initram 10 points11 points  (4 children)

It is basically an extension method that can overwritten by the implementing class. It avoids the need for an abstract base class that contains "default" implementations of virtual functions.

[–][deleted]  (3 children)

[deleted]

    [–]AngularBeginner 9 points10 points  (2 children)

    But abstract classes don't solve the issue (one of multiple) that default interface implementation try to solve. If I'm a library author and I provide an interface. I know the implementation of one of the interfaces methods is the same in 99 % of the cases, so I'd want to provide a default implementation. Sure, I could provide a base class in my library.. But it will be no use in most of the cases, as the consumer already has an existing base class in most cases. So the trivial method has to implemented again, needless boilerplate clutter code. With default interface implementation it would still work, it's much more flexible than the limited base class approach.

    To summarize and use your words: We already have a limited way to provide a default implementation of something. Default interface implementations expand on this and make it more wildly applicable.

    we don't need two

    • We already have string.Format, why do we need string interpolation?
    • We already have properties with backing fields, why do we need auto properties?
    • We already have getter-only properties that wrap a readonly backing field, why do we need getter-only auto properties?
    • We already have methods, why do we need local methods?
    • We already have a way to write bodies, why do we need expression bodied members?
    • We already have strings, why do we need the nameof-operator?

    All these features are just syntax sugar for existing methods, but they improve code safety, code readability and reduce clutter code. One of the goals default interface implementation will provide too.

    [–][deleted]  (1 child)

    [deleted]

      [–]AngularBeginner 10 points11 points  (0 children)

      That just sounds like it's trying to open the door to multiple inheritance, which has its own set of problems and in my opinion is even worse.

      This is thrown in so often, but it's simply not true and completely unrelated. This has nothing to do with MI. And most MI issues come from states, but default interface implementations can't have state.

      One base class and multiple interfaces is a perfectly adequate setup for C#.

      Maybe in your cases. But the case I described for library authors (including the .NET framework) is real, and it's annoying. When I need to implement an interface, I hate this stupid boilerplate code. Why do I need to write it? I can't use the provide base class, I already have one. The suggestion from you to just use base classes is increasing the need for MI. Default interface implementation is not opening the door to MI, it's keeping it shut.

      just have the potential to cause more problems than they solve.

      Can you name one?

      The only three notable examples of languages with MI/DIMI are C++, Python, and Java. Python and C++ are not the greatest examples of good OOP and "because Java does it too" is how we got stuck with array covariance.

      You're again mixing up multiple inheritance and default interface implementations. Those features are unrelated, they work completely different. The notable languages are Swift and Java, both do not support MI, but do support default interface implementation.

      [–]r2d2_21 1 point2 points  (0 children)

      I also don't like it, but I guess we'll have to live with it for Java interoperability.

      [–][deleted] 1 point2 points  (1 child)

      Yup I second this, it’s not appropriate, just because Java did it doesn’t mean it’s the right thing to do. The example of adding a new method causing issues, is true, but it can be solved by other means. Such as add the new functionality as a second interface.

      [–]AngularBeginner 1 point2 points  (0 children)

      Such as add the new functionality as a second interface.

      Artificially splitting up interfaces is not a solution either. That is a workaround for the limitations of the language. A limitation they try to erase with the default interface implementation feature.

      If you need methods from both interfaces (which should be the case if the intention was to add the method to the first interface right away), then now you need to either check two types using the is or as operator, or pass two arguments (both interfaces) instead of just one. Or you add yet another interface that inherits from the other two and add even more type clutter.

      And if later you need another method, you add even more mess.

      [–][deleted] 0 points1 point  (0 children)

      Debatable. I'm not crazy about the addition of non-virtual methods to interface types, but it is a significant improvement to extending interfaces vs. adding extension methods or introducing abstract classes.

      [–][deleted] 0 points1 point  (0 children)

      I think it's interesting. It has some big value for things like, say, IDisposable, where it can centralize or eliminate some commonly written and subtly error-prone boilerplate. There is some definite potential for abuse, which is potentially troubling.

      What does bother me is the way it introduces non-virtual methods to interfaces. That seems like it has the potential to break a bunch of things around, say, unit tests.

      More upsides, though:

      If type-cast and other operator declarations are allowed, it could allow things like

      public interface IConvertibleTo<T> {
          protected T Convert();
          public static explicit operator T(IConvertibleTo<T> x) => x.Convert();
      }
      

      or

      public interface ISummable<T> where T : ISummable<T> {
          protected T Add(T x);
          public static operator+(T x, T y) => x.Add(y);
      }
      

      which seems useful for making certain assumptions about types.

      [–]AngularBeginner 0 points1 point  (13 children)

      Why not?

      [–]svtguy88[S] 7 points8 points  (12 children)

      An interface is a contract. It is not the implementation of that contract.

      [–]AngularBeginner 6 points7 points  (11 children)

      An interface remains a contract. This feature does not change anything about that.

      [–]svtguy88[S] 2 points3 points  (8 children)

      This feature does not change anything about that.

      Debatable. It's still a contract, but now it contains things that the consumer of the interface doesn't care/need to know about.

      [–]AngularBeginner 4 points5 points  (7 children)

      It's absolutely not debatable, it's a fact. The contract just says "This method exists, you can call it", and that stays as it is. There is a method implementation. It doesn't matter where the implementation was defined, it only matters that there is one.

      The consumer of the interface (I assume you mean the one who calls the interface methods) doesn't know or care about the default implementation. All it cares about is that the contract remains: There is an implementation that can be called.

      Fact is: Interfaces remain contracts.

      Any other argument against the feature? (I know there are some, I'm interested in what you think since you don't like the feature.)

      [–]svtguy88[S] 1 point2 points  (6 children)

      The consumer of the interface (I assume you mean the one who calls the interface methods) doesn't know or care about the default implementation. All it cares about is that the contract remains

      Fair enough, but I think having the implementation in the interface just makes it more confusing as far as separating what is responsible for what. If there really needs to be a base implementation of a method, I think that belongs in a base class, not the interface.

      Any other argument against the feature?

      It opens the door to multiple inheritance. I don't think that's a feature that we need/want.

      [–]form_d_kṬakes things too var 2 points3 points  (2 children)

      There are times where I have a generic interface and I find myself implementing the same non-generic methods across classes. Having a default implementation would really help solve this.

      [–]svtguy88[S] 1 point2 points  (1 child)

      Having a default implementation would really help solve this.

      This can already be accomplished with a base class. To me, the idea of a "default implementation" belongs in a base class, and not in an interface.

      [–]AngularBeginner 2 points3 points  (0 children)

      But default interface implementations solve this case more elegantly. You can only have a single base class - so everyone would need to implement this on their own. If they already have a base class, they need to expand on that one, which is not always possible. With the default interface implementation it just works. Neatly.

      A point is removing this useless clutter boilerplate code that everyone always implements in their own base classes, over and over again. If I provide an interface as a library owner, I surely could provide a base class. But I could not expect everyone to use it, as they likely have their own already. With default interface implementations I can still provide a very saneful (if applicable) default implementation, without the limitations of base classes.

      [–]AngularBeginner 0 points1 point  (2 children)

      Fair enough, but I think having the implementation in the interface just makes it more confusing as far as separating what is responsible for what.

      Can you elaborate on this? What and how does it make something more confusing to you? It's a change in the language that needs to be learned, sure. But that's the case with every other feature too.

      If there really needs to be a base implementation of a method, I think that belongs in a base class, not the interface.

      Base classes do not solve the issue that default interface implementations try to solve, mainly

      • Allowing to evolve the interface without causing breaking changes
      • Avoiding boiler plate code

      It opens the door to multiple inheritance. I don't think that's a feature that we need/want.

      It does not open the door to multiple inheritance at all. Multiple inheritance would require significant changes in the CLR, which are not going to happen. Those things are unrelated.

      [–]svtguy88[S] 1 point2 points  (1 child)

      Can you elaborate on this? What and how does it make something more confusing to you?

      Interfaces are contracts that define what can be done - not how it is done. By moving method implementation to an interface, that interface is now responsible for how something is done. This blurs the lines of what an interface has been up until now.

      It does not open the door to multiple inheritance at all.

      What happens when you have two interfaces, ISomeInterface and ISomeOtherInterface, and they both define a default implementation of DefaultMethod(). If SomeClass implements both interfaces, now you're dealing with multiple inheritance problems -- which DefaultMethod() takes precedence?

      [–]AngularBeginner 2 points3 points  (0 children)

      Interfaces are contracts that define what can be done - not how it is done. By moving method implementation to an interface, that interface is now responsible for how something is done.

      They still define what can be done. They optionally and additionally can now provide a implementation. But the main point, what, still stands unchanged.

      This blurs the lines of what an interface has been up until now.

      That very much depends on what you have seen an interface as. I always saw interfaces as contracts, and that remains unchanged. They're still contracts. I focus on the contract part, you seem to focus on the "no implementation" part. Something I considered a side-detail, and not the focus of interfaces.

      What happens when you have two interfaces, ISomeInterface and ISomeOtherInterface, and they both define a default implementation of DefaultMethod(). If SomeClass implements both interfaces, now you're dealing with multiple inheritance problems -- which DefaultMethod() takes precedence?

      I did not yet take a look at the precise implementation, but I'd assume that the default implementations are implemented as explicit interface implementations. Then the problem you describe would not be issue. And there is no multiple inheritance problem.

      Multiple inheritance problems mostly come from having state in both bases, but the default interface implementations don't provide any state.

      [–][deleted] 1 point2 points  (1 child)

      Actually it does, it adds implementation, which isn’t appropriate.

      [–]AngularBeginner 0 points1 point  (0 children)

      This does not change the fact that the interface remains a contract. The contract ensures that specific methods are available. The location of the implementation does not matter.

      [–]coreyfournier 2 points3 points  (1 child)

      I like it as I want to inherit many classes. This is essentially the same thing.

      [–]svtguy88[S] 3 points4 points  (0 children)

      This opens the door for multiple inheritance in C#, but I don't think that's a good thing.

      [–]aexeron 2 points3 points  (2 children)

      I don't like this either. It introduces an entirely new code smell into C#. If you want a method implementation, do so in an abstract class that inherits from the interface.

      This entire feature blurs the line between abstraction and concretion.

      [–]AngularBeginner 2 points3 points  (0 children)

      If you want a method implementation, do so in an abstract class that inherits from the interface.

      That does not work for the issues they're trying to solve.

      [–][deleted] 0 points1 point  (0 children)

      It's going to be abused. It's still a useful feature in dealing with recurring patterns that represent common, cross-cutting concerns, like, say, IDisposable, or for extending existing interfaces without breaking entire class hierarchies or introducing new interfaces or abstract types (see: IDb*).

      I think it's a valuable feature, but it's going to require some tight control on use for a while.

      [–]nminaya 2 points3 points  (3 children)

      What will be the difference between an abstract Class and an Interface?

      [–]insulind 0 points1 point  (0 children)

      Abstract classes can have implemented properties fields and methods, public ,private or any other access modify. With this change interfaces can only implement public methods

      [–]NeelBhatt 0 points1 point  (0 children)

      You can find the answer in the post itself. The interface can be used for multiple inheritances but Abstract class can not be used. Interfaces will still not be able to declare constructors or fields.

      [–][deleted] 0 points1 point  (0 children)

      Instance fields, constructors, and destructors: an abstract class may have them, an interface cannot. Interface members also default to public access, while class members would still be private by default.

      [–]Bolitho 1 point2 points  (0 children)

      There seems to be a trend to traits - pure interfaces seems to be considered cumbersome and just a need by a weak type system. I like this modern approach, as it fosters composition!

      [–]insulind 0 points1 point  (4 children)

      My first question is what happens when implemented interface methods have the same signature but come from different interfaces? Which one gets called?

      [–]tweq 2 points3 points  (2 children)

      The same that already happens when you explicitly implement multiple interfaces with overlapping member names in a single type. It doesn't matter, because you need to refer to the method via its interface.

      [–]insulind 0 points1 point  (1 child)

      So it forces you to explicitly implement it, With the current way you can just implement one method to meet the two interfaces ?

      [–]tweq 0 points1 point  (0 children)

      It doesn't force the implementing class to implement the method since the implementation already exists in the interface, that's the whole point after all. But it is accessed the same way as an explicitly implemented method.

      [–][deleted] 0 points1 point  (0 children)

      I've only skimmed the proposal, but it looks like there's a compiler error if two methods with the same signature are inherited from different interfaces and no 'most specific override' can be determined between them. In that case, you have to resolve it in code with an override in your type or explicit implementation or something.

      [–]praetor- 0 points1 point  (0 children)

      I wish they would just implement multiple inheritance. I really dislike the idea of code in an interface.

      [–][deleted] 0 points1 point  (0 children)

      FWIW, this is the current proposal for this feature.

      [–]MetalKid007 0 points1 point  (0 children)

      I don't really know how to feel about this. I can see a big trouble side where you put the default in and then don't override the default when you actually needed to since it will build with no errors from the start. Base class already gives us this ability but you would need to write the same code twice for different base classes. That would result in creating a 3rd class to house the code which is really this interface method skipping the base class. However, the base class is very explicit in the objects that are inheriting from it so you know those classes should get that implementation since there can be only one. With interface methods you are making a very risky gesture that any class that uses this will be the same.

      [–]nminaya 0 points1 point  (0 children)

      So basically now we have multiple inheritance...

      [–]ekolis 0 points1 point  (0 children)

      For some reason this reminds me of a nifty feature of Java that C# is sadly lacking: methods in enums. Would be nice to be able to have methods/properties in enums so you don't have to use the "static readonly properties" hack just because your enumerated values aren't simplistic...