The C# interface isn’t exactly intuitive.
Interfaces, in general, are common. We use them all the time. You’re using at least one interface right now as you read this article.
Keyboards, mice, and screens are interfaces to your operating system. It’s the same concept with C# interfaces.
In this article, I’ll start with these familiar device?interfaces as a metaphor for explaining the C# interface. I’ll use examples and alert you to some of the pitfalls. You’ll also learn about industry best-practices when using C# interfaces.
By the end, you should have a clear picture of the C# interface.
Defining the C# Interface
Microsoft is a good source for a definition of a C# interface, but even then it helps to have other ways to relate the concept.? We’ll start with the textbook definition and take it from there.
1. The Textbook Definition
Microsoft’s documentation defines an?interface as follows:
“An interface contains definitions for a group of related functionalities that a?class?or a?struct?can implement.”
Even though this definition of an interface may seem straightforward, there’s a lot to it.? So let’s unpack that a bit.
2. Beyond the Textbook: Unpacking the Interface
An interface is a specific code construct in C#. It uses the keyword “interface” and contains “definitions” in the form of method signatures. Here’s a simple example:
interface IDefinable { string Define(); }
In this example, we have an interface named IDefinable. The convention for naming interfaces is to start with a capital I,?for “interface,” followed by an adjective ending in the suffix –able. It’s also a best practice to have one interface per file (the same goes for classes and structs).
A C# interface is a type. Use it in method parameters, members, method returns, and properties just like you would use any other type.? Here are some examples of using our IDefinable interface as a type:
//Declaring a field private IDefinable _definition; //Using it as a method's return value public IDefinable GetWord(int key) { IDefinable x = ...code to get x ... return x; } //Passing it to a constructor as a parameter public MyWord(IDefinable definable) { // do constructor stuff } //Using it as a generic type parameter List<IDefinable> list = new List<IDefinable>();
Typically, an interface will have the public access modifier.? The methods of an interface have no access modifiers, and are always implicitly public.
As you can see, our example only has one method: Define. Even the most commonly used interface in the language?IDisposable?has a single method signature.
Many interfaces have more than one method signature and even properties (which are just convenient forms of getter and setter methods).? Interfaces can also define properties, events, and indexers.
Here’s the definition for IDisposable, in case you were wondering:
namespace System { // // Summary: // Provides a mechanism for releasing unmanaged resources. public interface IDisposable { // // Summary: // Performs application-defined tasks associated with freeing, releasing, or resetting // unmanaged resources. void Dispose(); } }
This interface is used by a class that has some resource cleanup steps when an instance is no longer useful.
Now that we have a basic understanding of an interface, let’s take a look at some examples of how to use them . For starters, you need to implement the interface.
Implementing Interfaces
We’ve seen some examples of interface definitions. Now let’s put them into practice by implementing them in classes.
All this means is giving the class a method that matches the signature of the interface. Once you have this, you can use your class for whatever calls for the interface it implements.
First, since it’s one of the most common interfaces, let’s implement IDisposable. This example isn’t going to be functional, but it’ll give you an idea of how to use an interface.
using System; namespace Submain { public class UserData : IDisposable { private readonly IDbConnection _dbConnection; // ... other code public void Dispose() { _dbConnection.Close(); } } }
In this code, the UserData class implements IDisposable. IDisposable is in the System namespace, so we have using System; to import it. The colon (:) between the class name and the interface indicates that we want to implement the interface.
The pattern is, generally:
<class | struct> : <interface>
After you write that part, your IDE should give you some options for implementing the interface in the class. This saves a lot of time and avoids mistakes.
Take a look at the options for implementing IDisposable:
Remember, IDisposable is a special interface. It has extra powers! That’s why you see “…with Dispose pattern” in the menu. Normally, you would have only two options:
- Implement interface
- Implement interface explicitly
We’ll talk about explicit implementation later. For now, I’m just using the first option “Implement interface” to keep things simple.
You’re probably wondering about this extraordinary power of IDisposable. I won’t keep you waiting any longer. Here’s the trick.
Understanding IDisposable’s Special Powers
In the early days of IDisposable, you had to use some specific patterns to make sure your resources were disposed of properly. You had to call Dispose explicitly in your code.
But we’re only human.
Because of that, programmers sometimes missed it. They didn’t dispose of things correctly, and that meant memory leaks were more common.
Until one day, Microsoft introduced using (not associated with importing). After that, it was much easier to dispose of resources. Now we have cleaner code with fewer memory leaks.
Here’s how it used to work:
// inside some method body... DbConnection connection; try { connection = DbFactory.GetConnection(DbSettings.UsersConnection); connection.Open(); // use connection return result; } catch(DbConnectionException dbx) { ExceptionHandling.HandleException(dbx, Context); } finally { connection.Close(); connection.Dispose(); }
In the old days, we had to close and dispose the connection explicitly in the finally block.?This was the only way to ensure that the connection was disposed?and the resources were freed.
As you can imagine, this can get pretty complicated.
With the newer using statement, it’s much cleaner.
try { using( DbConnection connection = DbFactory.GetConnection(DbSettings.UsersConnection) ) { connection.Open(); // use connection return result; } } catch(DbConnectionException dbx) { ExceptionHandling.HandleException(dbx, Context); }
Now we don’t need the finally block.
Just pass whatever implements IDisposable to using, and it’ll scope the lifetime to the block that’s after it (between { and }).
Whenever control leaves that block, the .NET framework will call dispose on your instance. This makes the code much cleaner and less prone to error.
Ah, the power of the interface!
Anyways, let?s get back on track with interfaces in general. After all, you can really gain a lot once you learn to harness their true power.
The True Power of the C# Interface
The C# interface’s true power is that the consumer doesn’t need to know the details of the producer. It only needs to know that whatever it has conforms to the interface’s definition?that it has specific methods.
Because of this, interfaces are often described as contracts. But what does that mean?
Well, a contract says what you?ll agree to provide, but not necessarily how you?ll do it.
You might have a contract with your cell phone company that says you?ll pay $300 per month for service plus unlimited data for so many lines. You don?t get into the details of how they?re connecting you to the network.
All you have to know is that you get on the cell network and can use data as expected. It?s a similar thing with an interface
In the programming world, an interface allows us to use different classes and structs to fulfill the contract defined by an interface. All the consumer needs to know is that there?s a method that does X. It doesn?t care about the details.
Let me give you an example to show you what I mean.
Example
In this example, we?ll have two classes that implement a simple logging interface. Each class will log in its own way. One will write to a file, the other will write to the Debug log.
// define an interface public interface ILogger { void LogError(Error error); } // implement ILogger by logging to a file public class FileLogger : ILogger { public void LogError(Error error) { File.WriteLine(someFileUri, error.ToString()); } } // implement ILogger by logging to Debug log public class DebugLogger : ILogger { public void LogError(Error error) { Debug.WriteLine(error.ToString()); } } // program to the ILogger interface public class MyProgram { private ILogger _log; // inject whichever ILogger into this class via constructor public MyProgram(ILogger log) { _log = log; } public void Start() { try { StartInternal(); } catch(Exception ex) { Error error = Error.FromException(ex, Context); _logError(error); } } }
We can pass either a FileLogger or a DebugLogger to MyProgram.
ILogger fileLogger = new FileLogger(); var program = new MyProgram(fileLogger); //Or... ILogger debugLogger = new DebugLogger(); var program = new MyProgram(debugLogger);
Using an interface this way makes your code more flexible.
Now that you?ve seen some more concrete examples of the power of the interface, let?s turn our attention back to implementing an interface.
Implementing an Interface Explicitly
Now that you have a basic understanding of the C# Interface let’s get back to explicit implementation. An explicit implementation looks like this:
public class User : IDefinable { Definition IDefinable.Define() { // ... code to get definition return definition; } }
Notice two things here:
- IDefinable.Define – the pattern for explicit implementation is <interface>.<method>
- There?s no access modifier; it?s always public.
An interface can have more than one signature.
For example, IDefinable might have an overload for Define that takes an IFormatter. If there is more than one signature, we can mix and match implicit and explicit implementations.
Here?s a User class that implements the new IDefinable. It implements one method explicitly and the other method implicitly. Take a look:
public class User : IDefinable { // explicit Definition IDefinable.Define() { // ... code to get definition return definition; } // implicit public Definition Define(IFormatter formatter) { // ... code to get definition and use formatter return formattedDefinition; } }
You might have noticed that IFormatter doesn?t end in ?able.
Well, this is a pretty common divergence from the convention. Many interfaces don?t end in ?able. A few other examples from Microsoft are IDictionary<T>, IList<T>, and ILogger.
Key Difference Between Implicit and Explicit
There?s a key difference between implicit and explicit implementations. While you can call an implicit method on the class, you can only call an explicit method on the interface.
This screenshot will help tell the tale:
Both classes, User and Application, implement IDefinable. We?re declaring both user and application as the IDefinable interface type. We can call Define on both.
We can also call Define when we cast application to an Application type. In other words, Define is implemented and defined in Application. But, we cannot call Define on the User class. Why not?
Since we?ve defined the method explicitly in User, it is only available through the interface and not through the class.
Therefore, we cannot cast to User and call Define. With explicit implementations, we can push developers to use the interface rather than the class.
This difference reveals just one possible pitfall when it comes to interfaces. We might falsely assume that all code will use the interface just because we?ve implemented it in our class.
In fact, unless we implement the interface explicitly, there?s no guarantee. Even then, reflection can be used to get around the constraint.
Let?s look at some other pitfalls when it comes to interfaces.
Recognizing Pitfalls
Besides casting around interfaces, there are two other things you need to know about interfaces.
1. Avoid Implementing Too Many
Unlike inheritance where you can only inherit from one base class, you can implement multiple interfaces! But don’t get carried away trying to create a single class to do everything.
That’s not a good road to travel! You’ll have a large class that’s hard to change if you do.
2. Avoid Having Too Many Methods in One Interface
There’s an important principle in object-oriented programming called the “Interface Segregation Principle,” or ISP. It says that you should keep your interfaces focused.
When you implement an interface for a specific purpose, you don’t want to have to implement methods that aren’t likely to be used. It’s just wasteful!
Interfaces should contain methods that are likely to be used together.
Enabling Unit Testing
There are quite a few benefits to programming to interfaces. It makes your code more stable. It’s easier to contain change. And very importantly, it enables unit testing.
Let’s look at an example of how interfaces enable unit testing. It’s common to read data from a database, map the data to an object, and use that object in business logic.
Here’s the idea expressed in code:
public class Professor { //field declarations elided here on purpose internal bool IsTenureEligible() { Load(); bool isTenureEligible = // some complex logic return isTenureEligible; } private void Load() { if(_data == null) { _data = _db.Get<ProfessorData>(_id); } } }
In this case, we’d want to test the logic inside?IsTenureEligible.
To enable that, what should we use for _data, _db, and _id in this code? Once you understand interfaces, you’ll appreciate that _db should be an interface!
The others are just data so they can be concrete classes. Here’s more of this class:
public class Professor { private readonly IRepository<ProfessorData> _db; private readonly int _id; // constructor public Professor(IRepository<ProfessorData> db, int id) { _db = db; _id = id; } private ProfessorData _data; // method omitted }
And this makes the method testable because we can define how an IRepository<T> retrieves data. It doesn’t have to get it from a database. Instead, we can pass a test double to a Professor instance. A test double might look like this:
public class FakeProfessorRepository : IRepository<ProfessorData> { public ProfessorData Get<ProfessorData>(int id) { return new ProfessorData { YearsInService = 10, Rating = 9, // ...etc } } }
We can easily substitute any implementation of an interface. This fake repository will allow us to develop the Professor class, even without the underlying database.
Perhaps the DB isn’t ready to use yet! We can still develop our code with this double.
In Closing
Hopefully, you’ve enjoyed your journey through the C# interface. I’ve enjoyed writing about it and sharing the knowledge! Remember to program to interfaces and keep them focused. These key takeaways will allow you to write more flexible, cleaner code.
Thanks for reading and happy coding!
You might also be interested in further reading about other C# language concepts:
Learn more how CodeIt.Right can help you automate code reviews and improve the quality of your code.
4 Comments. Leave new
Which is the divergence from convention – ending in “-able”, like IDisposable, or not? I’d say IDisposable is the exception, because it has nothing to do with the reason why we depend on it or any classes that implement it. When we need an IRepository we depend on it. When we need to log something we depend on ILogger. No one ever needs to depend on an IDisposable. That interface exists only to tell us something we are supposed to do with an object that has nothing to do with our need for the object itself.
One could even say that it goes against purpose of an interface because it describes the implementation. If a class “depends” on an interface and that interface inherits IDisposable then the class must call Dispose() or put it in a using block. If we create another implementation of the same interface that doesn’t have any unmanaged resources, the class must implement Dispose anyway even though there’s nothing to Dispose, and the consumer must still call it.
WTF i did just read…
Hello Phil,
here is a ‘little corrected’ code for example of imp/expl. implementation of interfaces.
Your writing about C# Interfaces is excelent.
Tested example code >
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp5
{
public interface Ilogger
{
void LogError(Exception Error);
}
class Error
{
}
//Class with one metod explicit implementation from INTERFACE
public class FileLogger : Ilogger
{
// EXPLICIT impl.
void Ilogger.LogError(Exception Error)
{
Console.WriteLine("FIle LOGGER : " + Error.ToString());
}
public void filelog()
{
Console.WriteLine("FIle LOGGER2 ");
}
}
//Class with explicit implementation
public class DebugLogger : Ilogger
{
void Ilogger.LogError(Exception Error)
{
Console.WriteLine("DEBUG LOGGER : " + Error.ToString());
}
}
public class Myprog
{
private Ilogger _log;
//Inject Illoger into this class via constructor
public Myprog(Ilogger log)
{
_log = log;
}
public void Start()
{
int x = 5;
int y= 0 ;
int z;
try
{
z = x / y;
}
catch (System.DivideByZeroException Ex)
{
Exception error = Ex;
Console.WriteLine("Greska je " + error.ToString());
_log.LogError(error);
}
}
}
class Program
{
static void Main(string[] args)
{
// instanca FIleLogger preko INTERFEJSA Ilogger
// Instanties over Interface File Logger
Ilogger filelog = new FileLogger();
//var program = new Myprog(filelog);
Myprog program = new Myprog(filelog);
program.Start();
// instanca DebugLogger preko INTERFEJSA Ilogger
// Instanties over Interface Debug Logger
Ilogger debuglog = new DebugLogger();
//var program2 = new Myprog(debuglog);
Myprog program2 = new Myprog(debuglog);
program2.Start();
// instaciranje klase , nije dostupna interface metoda
// Instanties for class FileLogger, but out of scope for interface method for us, only public method of class
FileLogger myfl = new FileLogger();
myfl.filelog();
Console.ReadKey();
}
}
}
In IDisposable example, when i implement it, why no need to write own content in dispose() method?
Isn’t dispose() just a empty signature of the interface?
Or the .net library done for us? Thx