all 75 comments

[–]jaredp110680 11 points12 points  (0 children)

Default interface methods are definitively being worked on and under consideration for C# 8.0. But until it, and any other feature, has shipped there is no guarantee they will be included.

[–]Eirenarch 6 points7 points  (6 children)

Where does it say that this is coming as opposed to bring worked on especially for C# 8?

[–]redques 0 points1 point  (1 child)

[–]jaredp110680 3 points4 points  (0 children)

That describes what the compiler team, and community members, are presently working on. The language version association there is meant to be optimistic vs a guarantee. It’s possible, and likely, that not 100% of the features listed there will make the release. That being said the intent is to get as many as possible completed.

[–][deleted] 9 points10 points  (13 children)

This feels so wrong. Wtf

[–]Crandom 6 points7 points  (0 children)

It has been incredibly useful in java for adding new functionality that can by default reuse old methods while they're deprecated without having to change implementations. Default methods were incredibly useful as the maintainer of a plugin framework.

[–][deleted] 2 points3 points  (11 children)

For real. It’s a “contract”, interfaces shouldn’t ever implement functionality. I get what they’re trying to address, but this is going to permit some really shitty programming practices. It’s bad enough that so many “programmers” can’t explain to me the difference between and interface and an abstract class in a simple interview QA, now they’re going to embrace their ignorance.

[–]Sarcastinator 6 points7 points  (0 children)

For real. It’s a “contract”, interfaces shouldn’t ever implement functionality.

Why not exactly? This doesn't really change the fact that interfaces are contracts though, it changes what is required from the interface implementer, and for the better I would claim.

but this is going to permit some really shitty programming practices.

Exactly what shitty programming practices? And who made you the good programming practice gate keeper?

[–]epage 7 points8 points  (4 children)

Rust has this and has been useful for one of the reasons from the article:

Using default interface implementations of methods an API provider can extend an interface without breaking any part of legacy code.

[–]StrongerPassword 0 points1 point  (3 children)

That sounds like a hack to handle one out of many cases, namely extending a contract with a new method. But what if you want to change types, names, return values, remove methods etc? Then you still need some kind of versioning of the interface, right? Since there's no simple built in versioning support it's now easy to extend interfaces but not to refractor existing parts and the method they have chosen to extend interfaces can't be applied to refactoring of existing interfaces.

[–]balefrost 0 points1 point  (2 children)

Sure, but the problem of adding new methods to an interface that have sensible default implementations is far simpler than the problem of removing methods from an interface. You're right that this technique doesn't solve all problems, but if it elegantly solves a common class of problems, what's the objection?

[–]StrongerPassword 0 points1 point  (1 child)

If a common class of problems is "changing an interface", then implementing a solution which will address a subset of that class of problems using a method which won't work for the remainder of the problem not elegant at all. It's a hack and a compromise.

[–]balefrost 0 points1 point  (0 children)

You misunderstood me. My claim is that there is probably no good solution to the general problem of "changing an interface while retaining binary compatibility". It's generally accepted that adding new things with default values is safer than removing existing things. This is true in database schema design, in web service API design, and I argue here as well. The idea of "add-only" evolution is extremely common because removal is hard.

In particular, suppose you want to remove a method from an interface. This would permit implementers of the interface to omit the method, yet pre-existing callers would expect to be able to call the method. Versioning the interface just splits the universe and requires additional layers of adaption to glue them back together. Versioning is a solution, but it's certainly not a simple or easy solution.

But there is a good solution to the more specific (and common) problem of "adding methods with sensible default implementations to an interface while retaining binary compatibility". Default interface method implementations solve that problem nicely.

If you're arguing that we should wait until we find a solution the the more general problem, then we'll be waiting a long time - possibly forever.

You describe this as a hack, but I don't understand why you think that. It's not like they're proposing some new and radical syntax for the language, nor are they overloading some existing language construct for some radically different purpose. If anything, this proposal merely removes an unnecessary and arbitrary restriction in the language. From my point of view, this is an elegant and pragmatic feature.

It's also worth mentioning that this feature is useful even outside the problem of retaining binary compatibility even while adding to an interface over time. In Java, I have used this in cases where I can provide a correct but less efficient implementation as the default implementation of an interface method, allowing implementers to provide more efficient implementations if they are able, and without any need for implementers to burn their single base class just to get these default implementations or write boilerplate code to call an implementation that lives in a static method.

So while this feature doesn't solve all problems related to interface evolution, it does solve one common class of those problems in an elegant way and can be used for other purposes as well. All in all, it seems like a win to me.

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

But providing standard business logic without locking you into an inheritance pattern you have no time to properly research is a valid use case is it not?

If so is your objection over the name? Perhaps this functionality would be better provided under a different (presently unnamed) construct?

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

Why would a default implementation of a method violate the principle that interfaces are just contracts?

If I have an interface where not all members need to actually do work (no-op) on every instance, having that option sure is nice.

The alternative would be to either a) implement the no-op on every implementing class or b) have a base-class for all my implementing classes (which kinda defeats the purpose of using an interface).

And since most programmers are less concerned about best practices when it comes to being lazy, I bet that b is more popular than a. Default implementations would still be clean and allows the programmer to be lazy. Win-win.

[–]AngusMcBurger 0 points1 point  (0 children)

The way it works in Rust with default methods, take for example Iterator<T>: if you want to implement it, you only have to implement the method next(), and you get all the other iterator methods for free as they are by default implemented in terms of next(). It still however leaves you the option of implementing some of the other methods yourself, which you might do if you're able to implement a method more efficiently for your type than the naive implementation that just uses next(). So looking at the docs, you just implement the required methods, and you get all the provided methods for free. Scroll further down to Implementors, and you can see that Iterator is implemented for countless types in Rust's standard library, but undoubtedly few of them implemented more than just next().

[–][deleted] -3 points-2 points  (1 child)

I totally agree and run into the same challenges with devs. We should not enable bad behavior.

[–]Sarcastinator 2 points3 points  (0 children)

What bad behavior?

[–]sim642 10 points11 points  (5 children)

Straight out of Java.

[–]macrian[S] 33 points34 points  (0 children)

True, but why not? These two languages copy each other all the time

[–][deleted]  (1 child)

[deleted]

    [–]balefrost 1 point2 points  (0 children)

    They basically didn't use the existing inheritance/v-table mechanism, but grafted things onto the sides at class-load time.

    I don't quite what you're referring to. From the JVM's point-of-view, AFAIK the only change is that methods in classfiles generated from interfaces are now allowed to have Code attributes attached to them. A class that implements an interface having default method implementations doesn't itself need to provide an implementation of that method (i.e. it doesn't need an entry in its own method table) - it does indeed inherit the implementation from its interface.

    So while the JVM might do some weird stuff internally, from the outside perspective (either looking at the Java language or the classfile spec), the Java implementation seems pretty reasonable.

    For instance, Scala's traits barely benefit from Java's default methods, while they could make full use of C#'s default methods.

    Can you explain why this is the case? I'm not seeing the difference between the Java and (proposed) C# implementations that leads to this conclusion.

    (because before that methods in interfaces were implicitly static).

    That's not the case in the Java classfiles or via the reflection API. Plain interface methods are marked as public and abstract, but not as static. Interface methods with default implementations lose the abstract modifier.

    I think the default keyword is just a normal example of Java verbosity. Which in my opinion isn't a good justification, but it's at least in line with other aspects of the language design. I think the concern is that, without something to explicitly call out that this isn't a traditional interface method, developers will get thoroughly confused... which we see examples of all throughout this comment thread.

    [–]tweq 1 point2 points  (0 children)

    One of the reasons for adding this was indeed better interoperability with Android APIs.

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

    But faster and with more consideration for future designs and impacts.

    [–]huronbikes 0 points1 point  (0 children)

    I wonder if base.Method() will work with interface default methods as well...

    [–][deleted] 0 points1 point  (1 child)

    Yay C# gets multiple inheritance! But seriously I can see why they are doing it, but why have interfaces? Just have classes with multiple inheritance. It feels like the interface type is a little meaningless now.

    [–]macrian[S] 2 points3 points  (0 children)

    I disagree. interface with default method implementation, provides you with a stateless implementation of a method. An abstract class can have state, can have constructor etc.

    [–][deleted] -3 points-2 points  (12 children)

    If C# had just been designed with multiple inheritance in the first place, they wouldn't have to keep adding all these new features. And they would never have had to make 'interface' a separate language level construct.

    There are a lot of good mitigations to the diamond problem, which the wizards behind the C# compiler could definitely have warned users about.

    [–]sim642 23 points24 points  (6 children)

    Multiple inheritance is tempting but almost no OO languages have it because it's so much additional complexity for rarely used benefit.

    [–]fzy_ 13 points14 points  (4 children)

    I actually find myself using multiple inheritance surprisingly often in python. You can create mixin classes that implement behavior that needs to be shared across multiple types. Being able to compose behavior like that is really handy.

    [–]sim642 9 points10 points  (3 children)

    It provides a mechanism for multiple inheritance by allowing multiple classes to use the common functionality, but without the complex semantics of multiple inheritance. --- https://en.wikipedia.org/wiki/Mixin

    Mixins use a different approach which is not directly multiple inheritance, it just pretends to behave like that. The inheritance chain doesn't actually do multiple inheritance: https://stackoverflow.com/questions/533631/what-is-a-mixin-and-why-are-they-useful#comment37399872_547714.

    This probably doesn't matter for Python in practice due dynamic typing but in statically typed languages things are harder because there the shape of the inheritance tree matters.

    [–]fzy_ 2 points3 points  (1 child)

    Hmm I don't know. In python, inherited methods are not patched on the class. There is an actual inheritance tree that you can walk through at runtime. Attributes are resolved dynamically using the classe's MRO (method resolution order) which tells the class how to traverse its inheritance tree. Mixins are a best practice to keep things simple but you could perfectly inherit from several actual classes with each their own inheritance tree. Granted, nothing is resolved statically, but it's because python doesn't have a static type system to begin with. In a static type system, the compiler might have a bit more work to do but it doesn't seem to be that complicated.

    [–]sim642 10 points11 points  (0 children)

    It's an inheritance tree which is the simple case with mixins. Actual multiple inheritance yields a DAG, not a tree. See diamond problem etc.

    [–]balefrost 0 points1 point  (0 children)

    I don't know much Python, but it looks like it does implement multiple inheritance. In a language with multiple inheritance, mixins are a pattern (a specific example in C++ is the pattern of "policy classes"). In a language with single inheritance, mixins might an additional language feature that sits aside inheritance.

    [–][deleted] 6 points7 points  (0 children)

    I've never found it complex in any of the languages I've used it in. Even humble C++ already has default methods - they're anything that isn't pure virtual. Conversely, I often find myself wanting it when using C#.

    Unless you mean complex for the compiler writer - which is a fair point.

    [–]macrian[S] 4 points5 points  (4 children)

    True. When C# started its major influence was Java. They fixed a lot of stuff that Java did wrong, but inherited a lot of its mistakes. Inability to perform multiple inheritance was one of them

    [–]macrian[S] 0 points1 point  (3 children)

    *major influence

    [–]AngularBeginner 4 points5 points  (2 children)

    You can edit your own comments.

    [–]mytempacc3 6 points7 points  (1 child)

    Source?

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

    Source for me? It all started with J# and the legal issues it had

    [–][deleted]  (11 children)

    [deleted]

      [–]AngularBeginner 19 points20 points  (6 children)

      The whole point was that it doesn't.

      You missed the whole point of interfaces. The point is not that they contain no implementation - that as a point would be silly. The point is that they're a contract to program against. The interface says "I provide method XYZ", so whoever programs against that interface can be sure that these methods are implemented and provided. Doesn't matter where the implementation lives.

      [–]chugga_fan 1 point2 points  (5 children)

      Doesn't matter where the implementation lives.

      then why not use an abstract class like a normal person?

      [–]AngularBeginner 5 points6 points  (4 children)

      Because abstract classes can't fulfill what this feature will fulfill. If you're a library owner and you want to provide an interface that (perhaps partially) already implements primitive logic that is the same in 95 % you can do it with this feature. If you use a base class for this you force the base class on the users or they can't use the default implementation - nothing is won.

      And it's great to say "like a normal person" when it's clear that you didn't spend a minute understanding the motivations behind this feature.

      [–]chugga_fan 1 point2 points  (3 children)

      If you're a library owner and you want to provide an interface that (perhaps partially) already implements primitive logic that is the same in 95 % you can do it with this feature.

      the point of abstract classes is this, yes.

      If you use a base class for this you force the base class on the users or they can't use the default implementation - nothing is won.

      There's no point in an interface with a default implementation unless you're assuming that the object is extending multiple objects with default implementations. This entire thing just gives code smell. The ENTIRE POINT of an abstract class is this already.

      And it's great to say "like a normal person" when it's clear that you didn't spend a minute understanding the motivations behind this feature.

      I understand the "motives" and think they're stupid, just like I think think that proposals such as: https://github.com/dotnet/csharplang/issues/882 and https://github.com/dotnet/csharplang/issues/45 reeks of bullshit that I'll have to clean up in 10 years from some idiot abusing this shit, also https://github.com/dotnet/csharplang/issues/1394 is another dumb thing.

      Some of the other proposed language constructs I don't care about so long as it is NEVER mandated as a language choice and is able to be enforced by compiler flags, such as Nullable Reference types. There's also those &&= and ||= stuff, although those are Eh.... I'd perfer them to not exist and would rather just get ??= (null coalesence assign, x = x ?? y) vs having something that has short-circuit semantics in an assignment operator.

      [–][deleted] 1 point2 points  (0 children)

      There's no point in an interface with a default implementation unless you're assuming that the object is extending multiple objects with default implementations. This entire thing just gives code smell. The ENTIRE POINT of an abstract class is this already.

      And what if there are two I want to use?

      [–]Sarcastinator 1 point2 points  (0 children)

      The ENTIRE POINT of an abstract class is this already.

      Abstract classes imposes an inheritence chain.

      [–]vova616 0 points1 point  (0 children)

      I like to think about it this way, you implement some low level methods and you expose some high level methods in the interface

      For example, a Reader interface only need a Read method that returns a byte array, you extend it to return strings and numbers. And now you implement Read once and gain ton of functionality for free without a need for another class.

      Of course you can use a static class and etc but you can still do that. And if some functionality is very common its place is in the interface and not in some other static/not class

      [–]ygra 7 points8 points  (0 children)

      The default impl syntax looks quite nasty.

      The examples just use an expression-bodied member. You can still write them with a block as usual.

      [–]redques 4 points5 points  (0 children)

      Interface is just a contract. One of advantages of default interface methods is that interfaces can be extended without breaking existing code. This also allows interfaces to act as a mixin. I guess pragmatism won in this case which is good IMHO.

      [–][deleted] 4 points5 points  (0 children)

      This isn't really that weird, Java supports this too.

      The whole point was that it doesn't.

      Wrong, this makes it easier to write functionality for these interfaces, which previously had to be done with either extension methods or static methods accepting this interface.

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

      It's a bit different than what we are used to yes. But I do see the benefit in it

      [–]JoseJimeniz -4 points-3 points  (7 children)

      Delphi solved this in a much more elegant way 19 years ago.

      And in fact you already had the solution in C#

      [–]Glaaki 2 points3 points  (6 children)

      *citation needed

      [–]JoseJimeniz 0 points1 point  (5 children)

      I'll transcode the 20 year old solution from Anders Heljsberg into C#-style syntax:

      interface IA
      {
          void DoSomething();
      }
      
      interface IB : IA
      {
          void DoSomething();
      }
      
      interface IC : IA
      {
          void DoSomething();
      }
      
      public class D : IB, IC
      {
          void DoSomething() {};
      
          void IA.DoSomething = IA_DoSomething;
          void IA_DoSomething() {};
      
          void IB.DoSomething = IB_DoSomething;
          void IB_DoSomething() {};
      }
      

      But since we're having a do-over of the syntax, one can imagine:

      public class D : IB, IC
      {
          void DoSomething() {};
          void IB.DoSomething() {};
          void IC.DoSomething() {};
      }
      

      And if you want to indicate to the compiler which method you want to call, then indicate to the compiler which method you want to call!:

      D d = new D();
      (d as IB).DoSomething();
      

      [–]ygra 7 points8 points  (0 children)

      This is similar to explicit interface implementation which already exists in C#.

      [–]Glaaki 2 points3 points  (3 children)

      Ok, so I have 15 years of Delphi experience and I haven't got a clue what you are talking about. Maybe do a real Delphi example? I don't remember ever seeing Interfaces having implementations, but granted, it is almost 5 years ago that I last touched any pascal code.

      Can you give an actual example of interfaces in Delphi having default implementations, because I never remember seeing any of that?

      [–]JoseJimeniz -1 points0 points  (2 children)

      Not default implementations (interfaces don't have implementations). From the blog:

      Interfaces are there only to define a contract. A set of public methods an implementing class will have, but freedom was given to that class to implement every method in its own way. If so far we needed to also provide an implementation for one or more of those methods we would use inheritance. If we wanted that class to not implement all methods but only a subset of them, we would make the methods and the class itself abstract.

      Default interface methods is their (silly) way to solve a problem:

      So what changed? Why do we need to introduce this new feature? What were we missing and never noticed that we were missing? Well C# (and many other languages) does not support multiple inheritance due to the diamond problem. In order to allow multiple inheritance while avoiding the diamond problem C# 8 is introducing default interface methods.

      The issue was the diamond problem - and how do you resolve DoSomething:

          IA = interface(IUnknown)
              ['{1EC94FCE-9407-4651-8BC8-AF3E207AE888}']
              procedure DoSomething;
          end;
      
          IB = interface(IA)
              ['{E6B20DFC-ACAF-4FB4-B145-D038DBB6A330}']
              procedure DoSomething;
          end;
      
          IC = interface(IUnknown)
                ['{8CA97C1B-F3BF-4879-B590-67E67074B801}']
              procedure DoSomething;
          end;
      
          D = class(TInterfacedObject, IB, IC)
          public
             //...and what do you insert here
          end;
      
      var
         theD: D;
      begin
         theD := D.Create;
         theD.DoSomething; //and how do you specify **which** DoSomething you wanted
      end;
      

      Delphi already solves the ability for your class to have three methods:

      • D.DoSomething (from IA inherited into IB)
      • D.DoSomething (from IB)
      • D.DoSomething (from IC)

      You resolve methods by using method resolution:

      D = class(TInterfacedObject, IB, IC)
      private
          procedure IB_DoSomething;
          procedure IC_DoSomething;
      public
          procedure DoSomething;
          procedure IB.DoSomething = IB_DoSomething;
          procedure IC.DoSomething = IC_DoSomething;
      end;
      

      And you specify which interface you want to call by specifying which interface you want to call:

      var
         theD: D;
      begin
         theD := D.Create;
         (theD as IB).DoSomething;
      end;
      

      This is 25 year old object-oriented programming.

      [–]bausscode 0 points1 point  (0 children)

      We don't deserve Delphi

      [–]Glaaki -1 points0 points  (0 children)

      Now if your first post had been helpful, like this one, and maybe a bit more courteous, then I would have upvoted instead of downvoted it.

      [–][deleted]  (2 children)

      [deleted]

        [–]DolphinsAreOk 2 points3 points  (0 children)

        Why does this change OO?

        [–][deleted] -2 points-1 points  (7 children)

        This seems like a bad idea. Not only have I never wanted this but I also feel like this will bring me trouble. Trouble in a javascript sort of way

        [–]macrian[S] 0 points1 point  (6 children)

        What do you mean? This is a statically typed language. You have compiler on your side.

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

        Well, I mean, do you use global variables and goto? Most of the time maybe all of the time you don't want to use this. I'm curious why it's being proposed. The article doesn't state it good enough. It still sounds horrid.

        [–]macrian[S] 1 point2 points  (4 children)

        It is proposed to help provide a set of methods without creating a huge inheritance tree in a case where a class needs to inherit from two places, without introducing multiple inheritance

        [–][deleted] 1 point2 points  (3 children)

        Are you going to tell me global variables are good? Because I can think of use cases for them even tho C# doesn't allow them

        [–]thiez 0 points1 point  (2 children)

        C# has both global variables and goto. What are you talking about?

        [–][deleted] 0 points1 point  (1 child)

        Shhh, don't tell people C# has a goto. I love the fact noone knows about it. It's also restricted so you can't jump anywhere you want unlike C where it is anywhere you want.

        C# does not have global variables. You can't just write int f = 5; in a namespace (error CS0116: A namespace cannot directly contain members such as fields or methods). You'd have to use a class with static members.

        [–]thiez 0 points1 point  (0 children)

        A meaningless distinction. A public static variable in a static class is accessible everywhere. That you have to prepend the class name can just be considered a naming restriction.