Interface based programming in C#






4.89/5 (39 votes)
This article is aimed to help junior developers understand the Interface concept in C# language.
Introduction
Interface is one of the more overloaded and confusing concepts in object oriented programming. This article can help to understand Interfaces for those who have just started programming.
What is an Interface in C#?
Let’s start with the short definitions for Interface:
- An Interface is a way to achieve runtime polymorphism in C#.
- An Interface is a collection of properties and methods declarations.
- An Interface is a type declaration like a class or structure in C#; an Interface itself does not implement members.
- Interfaces are contract templates in which the contract members are declared.
What are the characteristics of an Interface?
Interfaces cannot be initialized directly as they do not have a definition for members, they should be implemented by either a class or structure.
Interfaces can contain any of the following type members or a combination of these four member types.
- Property
- Method
- Event
- Indexer
An Interface can be declared as either public or internal inside a namespace and can’t have any other access modifiers. An Interface can have any access modifier when declared inside a class or structure.
Members of an Interface are public by default and can’t have other access modifiers.
A class implementing an Interface must implement all the members declared in the Interface and they must be marked as public in the Implicit implementation. In the case of Explicit implementation, the Interface members can't have any access modifiers and they are private by default. If the implementing class is signed as abstract then the members of the Interface can also be signed as abstract in the class.
Why should we use Interfaces in C#?
Let’s start with a real world scenario to understand the Interface implementation.
Consider an application with a logging module to log application errors; the application should be able to address a customer specific error logging method. On the customer's side, Client A may expect the application to write logs to the file system, Client B may expect to log errors in the database, and Client C may go for the Windows Event log.
To address the above scenario, the application design should support swapping of the logging system with less or no developer effort, and this leads to writing a plug-n-play like module for the application.
We can address this scenario with a flexible and extensible software design using Interfaces. The rest of the article will deal with developing the system with notes on handling Interfaces in C#.
How to implement an Interface
Interface declaration
An Interface needs to be declared before it can be implemented in any class.
An Interface can be declared inside a namespace or a class with the keyword
interface
followed by the Interface name.
Example
namespace LoggingModule.Contract
{
public interface ILogger
{
/// <summary>
/// Write log information to logging source.
/// </summary>
void WriteLog(String Message);
}
}
In the above code, we have declared an Interface called ILogger
with a single method
WriteLog
inside the namespace LoggingModule.Contract
.
As the name implies, the WriteLog
method will have code for writing log information to
the configured logging source.
Interface implementation
A class can implement an Interface by suffixing a colon followed by the Interface name. A class can implement multiple interfaces by separating the Interface names with a comma.
Example
namespace FileSystemLoggingModule
{
public class FileSystemLogger : ILogger
{
public void WriteLog(string Message)
{
Console.WriteLine(Message + " ==> File system Logger is invoked!");
}
}
}
The above code shows the implementation of a single Interface ILogger
in
the FileSystemLogger
class. The WriteLog
method is defined with
the same input parameters and return type (void
) as declared in
ILogger
.
In the same way we can create a class for database logging and Windows EventLog
logging as shown below. All these three classes have implemented the contract
ILogger
which is demanding the implementation of the WriteLog
method.
namespace DatabaseLoggingModule
{
public class DatabaseLogger: ILogger
{
public void WriteLog(String Message)
{
Console.WriteLine(Message + " ==> Database Logger is invoked!");
}
}
}
namespace WindowsEventLoggingModule
{
public class WindowsEventLogger: ILogger
{
public void WriteLog(string Message)
{
Console.WriteLine(Message + " ==> Windows event logger is invoked!");
}
}
}
Now, let’s see how to use this interface in an application. We have declared an Enum (LoggingType
) to expose the available logging types.
The ApplicationLogger
class has a LogError
method which will check the logging type configuration and load
the appropriate logger implementation.
public enum LoggingType
{
FileSystem = 1,
Database = 2,
WindowsEventLog = 3
}
Code for loading logger based on logging type
namespace LearningInterface
{
public class ApplicationLogger
{
public void LogError(String strMessage)
{
LoggingType ConfiguredLoggingType = (LoggingType)
Convert.ToInt32(ConfigurationManager.AppSettings["LoggingType"]);
ILogger Logger;
switch (ConfiguredLoggingType)
{
case LoggingType.FileSystem:
Logger = new FileSystemLogger();
break;
case LoggingType.Database:
Logger = new DatabaseLogger();
break;
case LoggingType.WindowsEventLog:
Logger = new WindowsEventLogger();
break;
default:
Logger = null;
break;
}
if (Logger != null)
{
Logger.WriteLog(strMessage);
}
}
}
}
The LogError
method reads the logging type value in the app settings from
web.config. The ILogger
object is initialized based on the logging type set in app settings. By initializing
ILogger
based on configuration settings, we can easily switch the logging type of the application. If we need to change
the logging type in future, we don’t need to edit or recompile the application; changing
LoggingType
key in web.config will switch the application logging process.
A new logging type can be added to the application easily in the same way we did for
the database, Windows Event loggers.
There are better ways to achieve plug-n-play like software design with Interfaces and IoC. I have chosen the above approach to make it simpler to understand for beginners in C#.
Types of Interface implementation
An Interface implementation can be either Implicit or Explicit. In this article, we have used implicit Interface implementation.
Explicit implementation allows developers to tag the implementation details explicitly to
the Interface. Explicitly implemented members are prefixed with the Interface name as shown below.
namespace LearningInterface
{
public class ExplicitImplementation: ILogger, IErrorLogger
{
void ILogger.WriteLog(string Message)
{
Console.WriteLine(Message + " ==> Explicit implementaion of ILogger.WriteLog() invoked.");
}
void IErrorLogger.WriteLog(string Message)
{
Console.WriteLine(Message + " ==> Explicit implementaion of IErrorLogger.WriteLog() invoked.");
}
}
}
You will notice that, there are two implementations of the WriteLog
method with the prefix of
the respective Interface names. Explicit implementation helps identify the members more accurately and avoid shadowing/collision of members in multiple Interface implementations.
You can explicitly implement an Interface in Visual Studio as shown below:
Let’s see a scenario where we need to implement an Interface explicitly.
We know that C# does support multiple Interface implementations in a class. Our application has two interfaces:
ILogger
and IErrorLogger
, for different purposes, and have a method called
WriteLog()
with identical signature declared in those two interfaces. Now we have to implement those two Interfaces in a class.
First, we shall implement these Interfaces implicitly and see the problem with it.
Interfaces can be implemented implicitly in visual studio as shown in below.
Clicking on “Implement interface ‘ILogger’ will generate a skeleton of ILogger
members as shown below and can build successfully.
Let’s modify WriteLog()
to test it.
namespace LearningInterface
{
public class ImplicitImplementation: ILogger, IErrorLogger
{
public void WriteLog(string Message)
{
Console.WriteLine(Message + " ==> Implicit implementaion of WriteLog() invoked.");
}
}
}
The above code shows the implicit implementation of ILogger
and
IErrorLogger
in the ImplicitImplementation
class. This code will build with no errors as the
WriteLog()
implementation is compromising both ILogger
and
IErrorLogger
. Doing this way will spoil the purpose of interfaces as we have defined the same behavior for both Interfaces. So, we need to have
an option to identify or name Interface members explicitly.
Let’s invoke WriteLog()
from ILogger
and IErrorLog
objects and analyze the output.
Console.WriteLine("Implicit implementaion of an Interface in C#" + Environment.NewLine);
ImplicitImplementation objImplicit = new ImplicitImplementation();
//Create ILogger object
objLogger = (ILogger)objImplicit;
//Create IErrorLogger object
objErrorLogger = (IErrorLogger)objImplicit;
objLogger.WriteLog("Invoking from ILogger");
objErrorLogger.WriteLog("Invoking from IErrorLogger");
Console.ReadLine();
In the above code, we have created an object of the ImplicitImplementaion
class and created
ILogger
and IErrorLogger
objects from it. ILogger
and
IErrorLogger
objects are invoking the WriteLog()
method.
Output
When you run the code, you will get an output as shown above. Both Interfaces are invoking the same method.
This is not the expected behavior, ILogger
and IErrorLogger
must implement the
WriteLog
method differently and we should be able to invoke a specific implementation of
the WriteLog
method.
Let’s see how we can achieve it with an explicit implementation of ILogger
and
IErrorLogger
.
Console.WriteLine("Explicit implementaion of an Interface in C#" + Environment.NewLine);
ExplicitImplementation objExplicit = new ExplicitImplementation();
//Create ILogger object
objLogger = (ILogger)objExplicit;
//Create IErrorLogger object
objErrorLogger = (IErrorLogger)objExplicit;
objLogger.WriteLog("Invoking from ILogger");
objErrorLogger.WriteLog("Invoking from IErrorLogger");
Console.ReadLine();
We have implemented the WriteLog
method for both interfaces with the prefix of
the respective Interface names in the ExplicitImplementaion
class. An object for
the ExplicitImplemenatation
class is created and type casted to generate
ILogger
and IErrorLogger
objects.
So, when we invoke Logger.WriteLog()
, void ILogger.WriteLog(int ErrorCode, string Message)
of
ExplicitImplementaion
should be invoked. In same way, IErrorLogger.WriteLog(int ErrorCode, string Message)
should be invoked when
ErrorLogger.WriteLog
is executed.
Output
As you can see in the output, we are able to identify and invoke specific implementations of
WriteLog()
for ILogger
and IErrorLogger
.
Difference between explicit and implicit implementations
Implicit implementation
- Members are public by default.
- Members must be marked as
public
. - Members can be invoked directly through the object of the implementing class.
Example:
FileSystemLogger objFileSystemLogger = new FileSystemLogger();
objFileSystemLogger.WriteLog("");
Explicit implementation
- Members are private by default.
- Members can't have any access modifiers.
- Members can't be accessed directly, an object of the implementing class should be cast to the Interface type to access the members.
Example:
FileSystemLogger objFileSystemLogger = new FileSystemLogger();
ILogger objLogger = (ILogger)objFileSystemLogger;
objLogger.WriteLog("");
Points to consider while designing Interfaces
- An Interface should focus on common and related tasks that a module expects. Inclusion of unrelated methods will ruin the purpose of the Interface.
- An Interface should not include any method which is specific to a class; if there are specific methods declared in the Interface, it will become hard to implement the same Interface for other classes.
- An Interface should not declare too many methods. Too many methods make the implementation of Interfaces harder as the implementing class needs to implement all methods declared by the Interface.
- An Interface should be declared when there is need for abstraction by design. Too many abstractions may lead to increased complexity in maintenance and reusability of code.
Conclusion
Use of Interfaces will result in defining a clear relationship between modules in a system. Using an Interface is a good approach to write maintainable and reusable code in C#. Programming with Interfaces leads to developing a system with extensibility.
In this article, we have seen three different implementations of the logging process. In future if there is a need for switching to some other logging framework such as
log4Net, it can be done easily by creating a wrapper class for log4Net which will implement
ILogger
. There is no need for changing anything in other modules.
I hope that by understanding the examples discussed in this article, developers can understand the flexibility and the power that Interfaces can bring to system design.