Thursday, July 25, 2019

Interfaces In C# 8.0

As we all know, C# 8.0 was released a few days back, and it has many exciting features. Along with this release of C#, a significant amount of changes has happened in interfaces. So, in this article, let’s try to explore the new features and learn how we can use them in projects.
 
The agenda for this article is shown below:
  1. Interfaces Today
  2. Modifiers in Interfaces
  3. Default Methods
  4. Diamond Problem
  5. Conclusion

Interfaces Today

 
As a developer, we all make use of the interface, be it to create the loosely coupled component or to define the contract which should be implemented by the concrete class. Today’s interface methods never come up with the body or modifiers. It is the responsibility of the implementer class to provide the body and assign some modifiers to it. If the class does not implement the method, the compiler catches it and gives an error saying we need to implement the interface. A simple Logger example is below. 
  1. using System;  
  2. public interface ILogger {  
  3.     void Log(string Info);  
  4. }  
  5. public class TextLogger: ILogger {  
  6.     public void Log(string Info) => Console.Write("In base Logger");  
  7. }  
We have defined one interface called ILogger with one method Log (). We have a class called TextLogger which is implementing the ILogger interface. This is perfectly fine considering the current state of the design. Now, the problem occurs when we want to extend the ILogger and need to add some more information to it like below.
  1. public interface ILogger {  
  2.     void Log(string info);  
  3.     void Log(string typeofInformation, string info)  
  4. }  
Now, we will have an issue here as this new method must be implemented by the class where this interface is used and the compiler will show us an error until this is done like below.
 
Interfaces In C# 8.0
 
Now, considering this interface is been used by multiple clients, this will break many changes and it will be really painful to make these changes across the implementation. Depending on the places where this interface has been used, we need to implement this method across the classes so as to make our code compile. In order to overcome this, C# 8 came up with the idea of the Default Methods in interfaces.
 

Default Interface Methods

 
The main reason to get this feature in C# 8.0 is to provide the default implementation for the interface methods. So how we can do this? Let's see the following example. 
  1. using System;  
  2. public interface ILogger {  
  3.     void Log(string info);  
  4.     //Default implementation  
  5.     void LogInfo(string typeofInformation, string info) => Console.Write(typeofInformation + " " + info);  
  6. }  
  7. public class TextLogger: ILogger {  
  8.     public void Log(string info) => Console.Write("In base Logger");  
  9. }  
Here, we can see in the interface itself we have provided the implementation for the function. Here, our class TextLogger does not need to implement this method and there will not be any compile time error.
 
Now, in order to use this interface in our application, let's change our main method and let's see how we can use it. 
  1. class Program {  
  2.     static void Main(string[] args) {  
  3.         ILogger _logger = new TextLogger();  
  4.         _logger.LogInfo("Test""test"); // It will call the Default method of the interface.  
  5.     }  
  6.   }  
  7. }  
One interesting thing about default methods is that it will only work if the class is contextually treated as an interface. If we are not doing that, then the default method implementation will not be available for use.
 
Interfaces In C# 8.0
 
If we look at this feature closely, we can see that this can lead to the very well-known problem of the Multiple Inheritance which is famously called the Diamond Problem. By design, C# won’t face any issues as Multiple Inheritance is not possible with the classes and interfaces didn’t have the implementation of the methods but with the default method, this is going to change. Let’s see how it will be handled in C# 8.0.
 

Diamond Problem

 
The diamond problem is one of the biggest issues in languages, as C# classes do not support this feature which is a result of multiple inheritance, but interfaces can introduce this problem to some extent. Let’s see how C# handles them. The following diagram illustrates what the diamond problem is:
 
Interfaces In C# 8.0
 
The above figure depicts the diamond problem very well. Now, let's see with the default interfaces how this problem can arise and how C# handles it.
 
Let's design the interfaces like below.
  1. interface First {  
  2.     void WritetoConsole() => Console.Write("In First");  
  3. }  
  4. interface Second: First {  
  5.     void First.WritetoConsole() => Console.Write("In Second");  
  6. }  
  7. interface Third: First {  
  8.     void First.WritetoConsole() => Console.Write("In Third");  
  9. }  
  10. class FinalClass: Second, Third {}  
On writing this code, we will have the compile time error.
 
Interfaces In C# 8.0
 
Error message will be,
Interface member 'First.WritetoConsole()' does not have a most specific implementation. Neither 'Second.First.WritetoConsole()', nor 'Third.First.WritetoConsole()' are most specific. (CS8705) [DeaultInterfaceDemo]
 
In order to solve this problem as depicted in the Error itself, we need to provide the most specific override at the time of execution. The .NET design team has told us specifically about it: 
 
“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 the member at all.”
 
Let’s see how we can provide the default implementation and solve this diamond problem:
  1. using System;  
  2. interface First {  
  3.     void WritetoConsole() => Console.Write("In First");  
  4. }  
  5. interface Second: First {  
  6.     void First.WritetoConsole() => Console.Write("In Second");  
  7. }  
  8. interface Third: First {  
  9.     void First.WritetoConsole() => Console.Write("In Third");  
  10. }  
  11. class FinalClass: Second, Third {  
  12.     void First.WritetoConsole() {  
  13.         Console.Write("From Final class");  
  14.     }  
  15. }  

Modifiers in Interfaces

 
Traditionally, until the arrival of C# 8.0, we could not use the modifiers in the interfaces. Modifiers like private, protected, internal, public, and virtual are allowed. By design, all the default interface methods are made virtual unless we are making them private or sealed, All the members without a body are treated as abstract by default making it compulsory to be implemented in the concrete classes. 
  1. using System;  
  2. interface IInterfaceModifiers {  
  3.     //By Default default method is private  
  4.     virtual void DefaultMethod() => Console.WriteLine("Default method");  
  5.     //Private Default Method  
  6.     private void privatedefaultmethod() => Console.WriteLine(" private Default method");  
  7.     //Protected Default Method  
  8.     protected void ProtectedDefaultMethod() => Console.WriteLine(" protected Default method");  
  9.     // Public Default Method  
  10.     public void PublicDefaultMethod() => Console.WriteLine(" public Default method");  
  11.     virtual void VirtualDefaultMethod() => Console.WriteLine("Virtual Default method");  
  12.     abstract void AbstractDefaultMethod();  
  13. }  
  14. class InterfaceModifierDemo: IInterfaceModifiers {  
  15.     public void AbstractDefaultMethod() => Console.WriteLine("Abstract virtual method");  
  16. }  
  17. namespace DeaultInterfaceDemo {  
  18.     class Program {  
  19.         static void Main(string[] args) {  
  20.             IInterfaceModifiers i = new InterfaceModifierDemo();  
  21.             i.AbstractDefaultMethod();  
  22.             i.DefaultMethod();  
  23.             i.PublicDefaultMethod();  
  24.             i.VirtualDefaultMethod();  
  25.         }  
  26.     }  
  27. }  
When we run the above code we can see the following output on the console,
  • Abstract virtual method
  • Default method
  • public Default method
  • Virtual Default method
When we make a method virtual we can override that method in the interface itself, and we cannot override it in the implementation class.
When we make one method protected it is available in the inheriting interface rather than the implementing class. By default the members of the interfaces are abstract, which makes it compulsory for the implementing class to implement them properly.
 

No comments:

Post a Comment

Lab 09: Publish and subscribe to Event Grid events

  Microsoft Azure user interface Given the dynamic nature of Microsoft cloud tools, you might experience Azure UI changes that occur after t...