Default Interface Methods in C#

Default Interface Methods in C# with Examples

In this article, I am going to discuss Default Interface Methods in C# with Examples. Please read our previous article, where we discussed C# 8 new Feature Readonly Structs in C# with Examples. Earlier, one major difference between the abstract class and interface was that we could not add a default method in the interface once it was implemented in child classes. Now in C# 8.0, we can add the default method to the interface without breaking the existing implementation.

Default Interface Methods in C#

Before C# 8.0 interfaces only contain the declaration of the members (methods, properties, events, and indexers), but from C# 8.0 it is allowed to add members as well as their implementation to the interface. Now you are allowed to add a method with their implementation to the interface without breaking the existing implementation of the interface, such type of method is known as default interface methods (also known as the virtual extension methods).

The main benefit of the Default method is that it allows us to add new functionality to the interfaces of our libraries and ensure the backward compatibility with code written for older versions of those interfaces.

Allowed in the interface in C#:
  1. A body for a method or indexer, property, or an event accessor
  2. Private, protected, internal, public, virtual, abstract, sealed, static, extern
  3. Static fields
  4. Static methods, properties, indexers, and events.
  5. Explicit access modifiers with default access is public
Not allowed in the interface in C#:
  1. Instance state, instance fields, instance auto-properties
  2. override keyword is currently not possible, but this might be changed in C# 9

Note: Please use this feature carefully. Otherwise, it can easily lead to violating the single responsibility principles.

Syntax to provide Default Implementation for Interface Method in C#:

interface IDefaultInterfaceMethod
{
      public void DefaultMethod()
      {
            Console.WriteLine(“I am a default method in the interface!”);
      }
}

Example to Understand Default Interface Methods in C#
using System;
namespace Csharp8Features
{
    interface IDefaultInterfaceMethod
    {
        public void DefaultMethod()
        {
            Console.WriteLine("I am a default method in the interface!");
        }
    }
    class AnyClass : IDefaultInterfaceMethod
    {
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            IDefaultInterfaceMethod anyClass = new AnyClass();
            anyClass.DefaultMethod();
            Console.ReadKey();
        }
    }
}
Output:

Example to Understand Default Interface Methods in C#

If you look at the above code, you will see that the interface has a method i.e. DefaultMethod with implementation, and the implementer class in our example is AnyClass which has no idea about this default method. Now, Change the IDefaultInterfaceMethod to AnyClass while creating the instance inside the Main method as shown in the below code.

using System;
namespace Csharp8Features
{
    interface IDefaultInterfaceMethod
    {
        public void DefaultMethod()
        {
            Console.WriteLine("I am a default method in the interface!");
        }
        
    }
    class AnyClass : IDefaultInterfaceMethod
    {
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            AnyClass anyClass = new AnyClass();
            anyClass.DefaultMethod();
            Console.ReadKey();
        }
    }
}

Once you do the above changes, you will get a compile-time error CS1061: ‘AnyClass’ does not contain a definition for ‘DefaultMethod’, and no accessible extension method ‘DefaultMethod’ accepting a first argument of type ‘AnyClass’ could be found (are you missing a using directive or an assembly reference?) as shown in the below image

Default Interface Methods in C# with Examples

This error message proves that the inherited class does not know anything about the default method of an interface in C#.

C# Modifiers in Interfaces

Now, an interface in C# is extended to accept modifiers such as protected, internal, public, and virtual. By default, the default methods of an interface are virtual. If you want then you can also make them sealed and private by using the sealed or private modifier. Similarly, if you are not providing implementation to interface methods, then by default they are going to be abstract. For a better understanding of interface modifiers in C#, please have a look at the below example.

using System;
namespace Csharp8Features
{
    interface IDefaultInterfaceMethod
    {
        // By default, this method is virtual. The virtual keyword is not required here
        virtual void DefaultMethod()
        {
            Console.WriteLine("I am a default method in the interface!");
        }

        // By default, this method is abstract, so the abstract keyword not required here
       abstract void Sum();
    }
    interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod
    {
        void IDefaultInterfaceMethod.DefaultMethod()
        {
            Console.WriteLine("I am an overridden default method!");
        }
    }

    class AnyClass : IDefaultInterfaceMethod, IOverrideDefaultInterfaceMethod
    {
        public void Sum()
        {
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            IDefaultInterfaceMethod anyClass = new AnyClass();
            anyClass.DefaultMethod();
            IOverrideDefaultInterfaceMethod anyClassOverridden = new AnyClass();
            anyClassOverridden.DefaultMethod();

            Console.ReadKey();
        }
    }
}
Output:

C# Modifiers in Interfaces

inside the IDefaultInterfaceMethod interface, we defined one default implementation method i.e. DefaultMethod using the Virtual keyword. Using the virtual keyword is optional there as by default all the default implementation methods of an interface by virtual. Then we declared the Sum method with the abstract keyword and here the abstract keyword is optional as by default all the interface methods are abstract if we are not providing the implementation.

Then in the IOverrideDefaultInterfaceMethod interface, we override the DefaultMethod method. As the DefaultMethod method is a virtual method in the IDefaultInterfaceMethod interface, so we can override it. Then in the child class i.e. AnyClass, we implement both the interfaces and provide an implementation of the abstract Sum method.

Modifier Override Example in C#

The explicit access modifiers in the overridden method are not permitted. Let us understand this with an example. Please have a look at the below code. In the below example, inside the IDefaultInterfaceMethod interface, we have provided a default implementation to the DefaultMethod. And in the IOverrideDefaultInterfaceMethod interface, we are overriding the DefaultMethod and trying to provide the access modifier as public which is not allowed.

using System;
namespace Csharp8Features
{
    interface IDefaultInterfaceMethod
    { 
        virtual void DefaultMethod()
        {
            Console.WriteLine("I am a default method in the interface!");
        }
        abstract void Sum();
    }
    interface IOverrideDefaultInterfaceMethod : IDefaultInterfaceMethod
    {
        public void IDefaultInterfaceMethod.DefaultMethod()
        {
            Console.WriteLine("I am an overridden default method");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.ReadKey();
        }
    }
}

Once you do the above changes, you will get a compile-time error The modifier public is not valid for this item as shown in the below image.

Modifier Override Example in C#

Diamond Problem with Multiple Inheritance in C#

We can get the diamond problem, or ambiguity error because of allowing multiple inheritances with the default implementation in the interface method. Actually, it is a big problem for languages like C++ that allow multiple inheritances using classes. In C#, the multiple inheritances using classes are not allowed but allowed with interfaces. For a better understanding, please have a look at the below image.

Diamond Problem with Multiple Inheritance in C#

Let us understand the diamond problem in C# using the interface. Please have a look at the below example. Here, interface A declares one method called “Method” and this method is by default abstract. Then this interface is inherited by interfaces B and C and both the interfaces provide a default implementation for the method called “Method”. And finally, class D implements both B and C interfaces. Now, the diamond problem arises. The interface member ‘A.Method()’ does not have the most specific implementation. Neither ‘B.A.Method()’, nor ‘C.A.Method(). So, the compiler will get confused between the implementation from the B and C interface and hence give you a compile-time error.

using System;
namespace Csharp8Features
{
    interface A
    {
        void Method();
    }
    interface B : A
    {
        void A.Method()
        {
            System.Console.WriteLine("I am From Interface B");
        }
    }
    interface C : A
    {
        void A.Method()
        {
            System.Console.WriteLine("I am From Interface C");
        }
    }
    class D : B, C
    {
    }

    class Program
    {
        static void Main(string[] args)
        {
        }
    }
}

Here, we will get a compile-time error CS8705: Interface member ‘A.Method()’ does not have a most specific implementation. Neither ‘B.A.Method()’, nor ‘C.A.Method()’ are most specific as shown in the below image.

Default Interface Methods in C# with Examples

How to solve the Diamond Problem with Interfaces in C#?

The .NET development team has decided to solve the Diamond Problem by taking the most specific override at runtime.

Diamonds with Classes: A class implementation of an interface member should always win over a default implementation in an interface, even if it is inherited from a base class. Default implementations are always a fallback only for when the class does not have any implementation of that member at all.

Coming back to our example, the problem is that the most specific override cannot be inferred from the compiler. We solve this problem, by providing an implementation for the method “Method” in class D as shown in the below code, and now the compiler uses the class implementation to solve the diamond problem.

using System;
namespace Csharp8Features
{
    interface A
    {
        void Method();
    }
    interface B : A
    {
        void A.Method()
        {
            System.Console.WriteLine("I am From Interface B");
        }
    }
    interface C : A
    {
        void A.Method()
        {
            System.Console.WriteLine("I am From Interface C");
        }
    }
    class D : B, C
    {
        // Now the compiler uses the most specific override, which is defined in the class D.
        void A.Method()
        {
            System.Console.WriteLine("I am from class D");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            C c = new D();
            c.Method();
            Console.ReadKey();
        }
    }
}
Output:

How to solve the Diamond Problem with Interfaces in C#?

Real-time Example of Interface with Default Method in C#: Logging

The logger interface is a good example to explain the Default Methods of Inheritance in C#. In the below example, within the ILogger interface, we have declared one abstract method with the name WriteCore. And rest of the all-other methods of the ILogger interface have default implementations. The ConsoleLogger and TraceLogger classes are implementing the ILogger interface and provide implementation to the WriteCore abstract method. If you look at the below code, you can see that the code is compact and will not get any kind of warnings or errors. Before C# 8, it was mandatory to implement all the methods of an interface in a class unless that class is declared as an abstract class, and this might make your code DRY.

using System;
namespace Csharp8Features
{
    enum LogLevel
    {
        Information,
        Warning,
        Error
    }

    interface ILogger
    {
        void WriteCore(LogLevel level, string message);
        void WriteInformation(string message)
        {
            WriteCore(LogLevel.Information, message);
        }
        void WriteWarning(string message)
        {
            WriteCore(LogLevel.Warning, message);
        }
        void WriteError(string message)
        {
            WriteCore(LogLevel.Error, message);
        }
    }

    class ConsoleLogger : ILogger
    {
        public void WriteCore(LogLevel level, string message)
        {
            Console.WriteLine($"{level}: {message}");
        }
    }
    class TraceLogger : ILogger
    {
        public void WriteCore(LogLevel level, string message)
        {
            Console.WriteLine($"{level}: {message}");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            ILogger consoleLogger = new ConsoleLogger();
            consoleLogger.WriteWarning("Cool no code duplication!"); 

            ILogger traceLogger = new TraceLogger();
            traceLogger.WriteInformation("Cool no code duplication!");
        }
    }
}
Output:

Real-time Example of Interface with Default Method in C#: Logging

Note: We can now add members to interfaces and provide an implementation for those members. This language feature enables API authors to add methods to an interface in later versions without breaking source or binary compatibility with existing implementations of that interface. Existing implementations inherit the default implementation.

In the next article, I am going to discuss Pattern Matching Enhancements in C# 8 with Examples. Here, in this article, I try to explain Default Interface Methods in C# with Examples. I hope you enjoy this Default Interface Methods in C# with Examples article.

Leave a Reply

Your email address will not be published.