loading...

Default Interface Methods in C#: Love 'em or Hate 'em?

onyxprime profile image Ryan Overton ・4 min read

This post is part of the 2019 C# Advent Calendar. Check out the link for other great posts!

I don't know I've ever run across a feature in C# that brought the immediate question, "Why would they do that?", but such was the case with almost everyone I talked to about one of the new features in C# 8.0, default interface methods. Wait... what?? Did he just say you can now add implementations in interfaces?

That's right! With C# 8.0, we can now add members to interfaces and implementations for those members. This means interfaces are no longer just a contract with declarations of methods, properties, indexers and events, but can now have private members, static members, protected members, virtual members and default implementations for these members.

When I first heard about this new feature, I was excited... until I wasn't. More on that in a minute. I finally had a way to update an interface to include a new method, one at least I was happy with.

Let's take a look at a real world example where I recently ran in to a situation perfectly suited for this new feature. I was creating an interactive chat bot for my Twitch stream, Developers Garage.

Real-World Example

The chat bot acts on commands entered by the viewers. In order to ease the addition of new commands, the common elements of the commands were extracted to the interface below and all commands implement this interface.

    public interface IChatCommand
    {
        IEnumerable<string> Command { get; }

        string Description { get; }

        TimeSpan? Cooldown { get; }

        Task Execute(IChatService service, CommandArgs args);
    }
Enter fullscreen mode Exit fullscreen mode

As new features were added, I realized there were commands I wanted to be executed by me only and I didn't want them to be able to be seen as available commands by viewers. My solution–add a new method returning whether or not a command can be listed.

    bool CanBeListed();
Enter fullscreen mode Exit fullscreen mode

Before we look at how the new default interface method feature helped us implement this solution, let's look at how we could have done it.

The Old Way

To add this new method before, we had 2 options:

  1. Update the existing interface and then update all classes implementing this interface.
  2. Create a new interface with the new method and have it inherit from the original interface.
    public interface IChatCommand
    {
        IEnumerable<string> Command { get; }

        string Description { get; }

        TimeSpan? Cooldown { get; }

        Task Execute(IChatService service, CommandArgs args);
    }

    public interface IChatCommandExtended : IChatCommand
    {        
        bool CanBeListed();
    }
Enter fullscreen mode Exit fullscreen mode

The second option is typically the preferred method, but as you continue to extend the interface, the list can grow to be unmanageable. The new default implementation feature in C# 8.0 alleviates the never ending cycle of interface extension.

The New Way

With this new feature, we no longer have to create an extended interface, but add our new method to the existing interface and provide it a default implementation.

    public interface IChatCommand
    {
        IEnumerable<string> Command { get; }

        string Description { get; }

        TimeSpan? Cooldown { get; }       

        bool CanBeListed() => true;

        Task Execute(IChatService service, CommandArgs args);
    }

    public class ProjectCommand : IChatCommand
    {
        public IEnumerable<string> Command { get; } => "project";

        public string Description { get; } => "Returns current project";

        public TimeSpan? Cooldown { get; } => TimeSpan.FromSeconds(10);

        public Task Execute(IChatService service, CommandArgs args)
        {
            await service.SendMessage(currentProject);
        }
    }
Enter fullscreen mode Exit fullscreen mode

This will prevent existing IChatCommand implementations from breaking while still providing an implementation when called upon by other code.

And with every new command created we have the option of overriding the default implementation and hiding our new command from being listed.

    public class ProjectCommand : IChatCommand
    {
        public IEnumerable<string> Command { get; } => "project";

        public string Description { get; } => "Returns current project";

        public TimeSpan? Cooldown { get; } => TimeSpan.FromSeconds(10);      

        public bool CanBeListed() => false;

        public Task Execute(IChatService service, CommandArgs args)
        {
            await service.SendMessage(currentProject);
        }
    }
Enter fullscreen mode Exit fullscreen mode

So Why Aren't You Excited Anymore?

I mentioned earlier in the post I wasn't as excited about the new default interface methods feature as I was when I first heard about it, and that's because the code generation action within Visual Studio doesn't generate these methods.

I use this quick action a LOT when I'm developing and having to remember there is a default implementation on an interface is just asking for trouble. You can also turn it around and if a new member of the team, or junior developer, someone not super familiar with the code base, could totally gloss over it and find themselves spinning their wheels trying to figure out what's going on.

So What's The Solution

Yes, I know I'm being a little nit picky and this doesn't really have anything to do with the new feature, but that's because I believe this is a great addition to C#. The tooling, in my opinion, is one of the things that make writing C# so enjoyable. So, I keep it to just as high a standard as I do the C# language.

My solution would be to make the code generation action configurable by adding an option to automatically implement default implementations.

Conclusion

I am a fan of default interface methods feature and based on conversations I've had with other developers, I'm one of the few. There are other scenarios this new feature can solve and you can find out more about them in the documentation here.

They're a lot more great features brought to us in C# 8.0 and I encourage you to go check them out here.

What are some of your favorite, or disliked, features in C# 8.0? Let me know in the comments below.

Discussion

pic
Editor guide
Collapse
tomtheisen profile image
tomtheisen

This means interfaces are no longer just a contract with declarations of methods, properties, indexers and events, but can now have private members, static members, protected members, virtual members and default implementations for these members.

As far as I've been able to tell, interfaces in C#8 can do none of:

  1. private members
  2. static members
  3. protected members

And all interface members have always been virtual, in the sense that they're virtually dispatched.

As you can guess, I don't really understand the panic over this feature. I think it's great.