In this article, we will learn about default interface methods in C#.
So, let’s start.
Introduction to a Default Interface Method
The versions before C#8.0 did not allow an implementation code inside interfaces. From C#8.0 we have a default interface method feature that enables us to have a member’s default implementation inside an interface. It is also called the virtual extension method.
So, let’s start with an ICalendar
interface:
public interface ICalendar { public DateTime Date { get; set; } public string ShowMessage() { return "Default Calendar"; } }
In this interface, we can see a ShowMessage
method with its body. This is known as the default interface method.
Key Points to Remember
Let’s see what are the key points that we have to keep in our mind when we work with default interface methods:
- We can declare static fields, methods, properties, or events
- We can have a body of methods, indexers, properties, and events
- We can’t instantiate fields or properties
Access Modifiers in a Default Interface Method in C#
Explicit access modifiers are accessible to everyone by default. Those can be public
, private
, protected
, sealed
, static
, virtual
, extern
, abstract
, and internal
. Any member with its implementation inside the interface is a virtual member so that deriving interface can override them. The sealed
or private
access modifiers prevent this.
Any member without its implementation and only declaration are abstract. We can’t have explicit access modifiers in the overridden method.
When to Use a Default Interface Method in C#
Before C# 8.0, an interface contained only declarations of the members, and once implemented in any child class, we couldn’t modify it. From C#8.0, if we want to add a new functionality/method to the existing interface and we also want our existing code to continue to work without breaking the implementation of interfaces in the production, we can use default interface methods. It provides the traits concept of OOPS (Object-Oriented Programming System).
Let’s understand this with an example.
In production, we have a code where the IYearCalendar
interface derives from the previous ICalendar
interface. We can add new methods IsLeapYear
and ShowMessage
inside IYearCalendar
without breaking the existing code in production:
public interface IYearCalendar : ICalendar { public bool IsLeapYear() { if (Date.Year % 400 == 0) return true; if (Date.Year % 100 == 0) return false; if (Date.Year % 4 == 0) return true; return false; } string ICalendar.ShowMessage() { if (IsLeapYear()) return $"{Date} is a leap year"; else return $"{Date} is not a leap year"; } }
These methods act as default interface methods,IsLeapYear
checks if a given year is a leap year or not in the interface only and ShowMessage
displays the output.
Handles a Diamond Problem or an Ambiguity Problem in Multiple Inheritance
When any class inherits two or more interfaces, and these interfaces have members with the same names, then the compiler can’t decide the preference among the same named methods. This scenario in multiple inheritances in interfaces poses an ambiguity problem known as the diamond problem.
To solve this problem, from C#8.0 interface methods are implemented in child classes or the caller passes the state as an argument.
Let’s continue with the example…
We have another interface called IMonthCalendar
which inherits the same ICalendar
interface, which IYearCalendar
inherits from:
public interface IMonthCalendar : ICalendar { public bool Is31DaysMonth() { if (DateTime.DaysInMonth(Date.Year, Date.Month) > 30) return true; return false; } string ICalendar.ShowMessage() { if (Is31DaysMonth()) return $"{Date} has 31 days"; else return $"{Date} does not has 31 days"; } }
This interface contains the Is31DaysMonth
method, which checks if the given month has 31 days or not and ShowMessage
displays the output.
Now, we can create the MyCalendar
class that inherits both IYearCalendar
and IMonthCalendar
interfaces:
public class MyCalendar : IYearCalendar, IMonthCalendar { public DateTime Date { get; set; } DateTime ICalendar.Date { get => Date; set => Date = value; } }
As we compile this code, we will get an error:
We get this becauseShowMessage
does not have the most specific implementation. Neither IYearCalendar.ShowMessage
nor IMonthCalendar.ShowMessage
are the most specific. So the compiler doesn’t know which method needs to be implemented.
Hence, we can solve this by implementing ShowMessage
in the MyCalendar
child class:
public string ShowMessage() { return $"Today is {Date}"; }
This class implementation overrides the default implementation of the interface and the compiler uses this class implementation of the ShowMessage
method.
Conclusion
For a good design, we can use the default interface method but we need to be careful in the real-world implementation of this feature. It should not violate the Single Responsibility Principle of the SOLID principles.