Sunday, June 23, 2024 22:14

Table of contents >> Object Oriented Programming > Interfaces


Interfaces, just like C/C++ pointers, are one of those topics that beginners, and even intermediate programmers are afraid of, because they do not understand them. In fact, the truth is, they are simple to understand, and the real difficulty comes when asking the question “why should I use them/where should I use them?”. In this lesson, hopefully, I will demystify them.

So, lets start with the hard part, the academical definition, according to MSDN: An interface contains definitions for a group of related functionalities that a non-abstract class or a struct must implement.

Not much to gather from that. Now, let’s explain same thing, in simple words. Suppose we have three machines: a lawn mower, a vacuum cleaner and a microwave oven. What do these machines have in common? Apparently nothing. Though, on a closer inspection, we can assert that all of them have two buttons: a Start and a Stop one. And that is all you need to know to understand interfaces. Three different objects, with three different ways of performing some actions, but all of them with two similar buttons, that we use to perform those actions (starting and stopping). When we press the Start button, we don’t really care what object is the one that implements them, and we also don’t care how the object performs the action of starting up. We only know that it offers us an interface for starting, interface that guarantees us the object can be started.

For this reason alone, we can conclude the first property of C# interfaces: they provide consistency. If we have an interface that declares a Start and a Stop behavior, ANYTHING that implements this interface is GUARANTEED to offer us the ability to start and stop, using the same common concept.

The second property of interfaces is that they represent a contract. In the real world, when you buy a house, you sign a contract through which you agree to pay a monthly fee until the cost is covered. In C#, an interface is just like a contract that basically says “I hereby agree to implement whatever methods this interface declares” ( in our case, the Start() and the Stop() methods).

Third, interfaces are really, really simple. They do not provide implementations, at all. In fact, interfaces don’t do anything, except to provide data types and names.

Fourth, just like abstract classes, interfaces cannot be instantiated. In fact, interfaces by definition are also abstract.

By convention, interfaces names start with a capital I (I as in Interface), followed by a noun or adjective.

Lets create a concrete interface example. It is always recommended to place an interface in a separate file. So, let’s do this. In Visual Studio, right click on the project name inside Solution explorer tab, and chose Add – New Item from the menu that appears:

Visual Studio - adding a new project itemBe careful to right click on the project name, not the solution one. Next, in the dialog that appears, select Interface:

Addin a new interface in Visual StudioAnd give it a meaningful name. I will name mine IMachine. I will also define the codes that will allow me to implement the Start and the Stop behavior:

And that is all of it. You might look at it and say “well, it doesn’t do anything…”, and you’d be right. Interfaces do nothing. They simply define different members, in this case, Start() and Stop(), and what they return (a bool), but they don’t implement any functionality.

Now, let’s create a class named Car, to which we will implement the IMachine interface:

You will quickly notice that we implement an interface in the same way we perform inheritance, by using the : operator. Though, you should remember, when used with a class, it is called inheritance, when used with an interface, is called implementation. So, our Car object now implements the IMachine interface. Except that it doesn’t, at least in this stage. You will notice that you will actually get errors in Visual Studio: Error CS0535 ‘Car’ does not implement interface member ‘IMachine.Start()’. And this explanation should already be obvious, since I already said that an interface is a contract through which we agree to implement any functionality declared in that interface. Compiler is doing nothing more than complaining that we are in violation of our contract agreement. We agreed to implement that functionality, and we didn’t! Therefor, we must do so. One way of automating this is by hovering the mouse cursor over the name of the interface, where the squiggly red line indicating an error is, and clicking on the little button that appears:

Automatically implementing an interface in Visual StudioIf you click on “Implement interface”, you will suddenly get these codes:

Now, the compiler is quite happy, we fulfilled our contract agreement with the IMachine interface, and implemented all its behaviors. Now, inside the Car class, we can start providing the actual functionality that happens when we start or stop it:
At this point, you will be able to compile and run the program without errors, but you will also probably scratch your head and say “mmmmyeah, but I’m not really sure what that benefits us…”, and you will be partially right. We could have implemented the Start() and Stop() methods directly, without having to create an extra file for the interface. But bare with me, I’ll explain why we did it shortly. For now, let’s add another class called LawnMower, also implementing IMachine:
Now, inside the Main() method, lets instantiate both these objects and call the methods they implemented:
We will get presented with this screen:

Interface behavior implementedNow, suppose we would have a lot of cars and a lot of lawnmowers to start and stop. It would soon become tedious to call Start() on them. Instead, we can use method overloading by adding a method for starting which accepts a Car parameter, and a method for starting that accepts a LawnMower:

This is certainly better, but it’s still not great. And this is where interfaces become useful. Instead of creating two Start() methods, each accepting a different machine type as a parameter, I can declare a single Start() method that accepts an IMachine parameter!
And I know many of you will actually look at the code above and say: “well, if the Start() method accepts an IMachine parameter, and since interfaces can’t be instantiated, this code is totally useless. I cannot create an IMachine instance, to pass as a parameter when I call the Start() method”. I thought the same exact thing when I first saw it back in the day. And my “Evrika!” moment came when I realized that the Start() method is not actually asking for an IMachine, it asks for anything that implements IMachine. We know Car implements IMachine, and we know LandMower implements IMachine, therefor they are candidates to pass in.

Now, because IMachine is a contract that states that anything implementing it is guaranteed to have a Start() method, the compiler will let me call the Start() method on the machine parameter. It knows that the provided parameter must contain a Start() method, otherwise the program wouldn’t compile, since we would be in violation of our contract, like described above.

And here you should have the “Evrika!” moment. What does this method start? It’s able to start ANYTHING that implements the IMachine interface! If it’s a lawnmower, a microwave oven or even a UFO, if they implement IMachine, this method can start them. It doesn’t really care what it starts, it only knows that it will receive parameters that have the ability to be started.

Lets see it in action:

With this result:

And, if you haven’t figure it out already, that is also polymorphic behavior. And this also let us future-proof our applications. If at some point we decide that we also want to add an Airplane class, we don’t need to modify the Start() method, or create another overload. We only need to make Airplane class implement IMachine, and we would be able to start it using the same existing method, without changing a single bit of code.

We can observe this polymorphic behavior even further on, in this example:

Now, car and mower are what’s called an interface variable type, and they now accept to be initialized with anything that implements IMachine.

So, now, let’s revision some key aspects:

  • interfaces are contracts, they guarantee that anything that implements them will implement all the functionality they declare
  • an interface declares just properties, methods, indexers and events. It is up to the class that implements it to define exactly what the methods will do.
  • interface members are public by default. In fact, we are not even allowed to define an access modifier. Interface members define only return types and names.
  • if you have two interfaces, both with a method named Start(), you can use what’s called explicit implementation, in which you prefix the method name with the name of the interface, followed by a dot. Example: public bool IMachine.Start()
  • unlike inheritance, classes can implement multiple interfaces
  • interfaces can themselves inherit other interfaces. The class that implements the interface is also forced to implement all the members of the interfaces that the implemented interface inherits.

As a last point, let’s differentiate interfaces of abstract classes. The purpose of an abstract class is to define some common behavior that can be inherited by multiple classes, without implementing the entire class. Interfaces force the class that implements them to implement all their members. Interfaces are like skeletons. If you want to build a human, you should use that skeleton. Abstract classes are like skeletons, but with some meat on them as well. It’s just there to make your work easier. You can consider an abstract class to be an interface which already has some implementation.

To conclude this lesson, you might hear at some point in your programming career about the concept of “programming to an interface”. The topic is rather heavy to explain in it entirety, but the core concept is already described in this lesson: instead of creating a method that accepts a class as a parameter, be it a Car or LawnMower, we created a method that accepted an interface, and thus, we made the code more modular, decoupled, generic. To read more about “programming to an interface”, check these amazing answers on StackOverflow.

This is all about interfaces. I hope that now you have a solid knowledge not only about what they are, but also about why and when you should use them.

Tags: , ,

Leave a Reply

Follow the white rabbit