Polymorphism

"You promised to explain where interfaces are used in C#," I reminded Noname after a quick lunch break.
"Ahh, yes," Noname replied. "I like how curious you are!"
"It's not curiosity; I need to isolate Commander," I said. It was strange that Noname didn't know my true intentions, given that he could read my mind.

"Let's start with high-level concepts," Noname said. "Imagine you just climbed into a new car. You take the driver seat, keys in hand. What do you need to know to start this vehicle and drive somewhere?" Noname asked.
"Basically, nothing. I just turn the key and drive. Why do you ask such weird questions?" I asked.
He ignored my question and continued, "Do you need to know what type of brakes the car has in order to use them?"
I answered, "Nope, when I press the brake pedal, the car brakes. That's all I need to know."
"Exactly! What about the fuel; do you need to know whether it runs on electric or petrol?" Noname continued his line of questioning.
"Not as long as whatever it runs on is full," I said. "I can drive it regardless of what type of engine it has. The experience might be different, but I'd still be able to drive it," I explained.
"Super! Now, what gives you the ability to drive any car? Why can't you program in any programming language, but you can drive any car?"
"Because cars have common rules of how to use them. You turn a steering wheel, and the car turns, you press a pedal and car accelerates. What are you trying to say, Noname?" I was puzzled.

"I'm saying that all cars have the same interface . You don't know exactly how the car works under the hood, but you know how to use the common interface that is implemented in all cars. The existence of the interface is what gives you the abiity to drive any car," Noname exclaimed. This was the key thought that he was heading to.
"Okay, I think I get what you mean. What does it have to do with... wait, you're saying that interfaces in C# solve the same problem?" It took a while, but I had finally arrived at the answer.

"Exactly, Teo. You have a very sharp mind. An interface provides a common way to interact with all classes that implement it. The ability to work with different classes via the same interface is called polymorphism. " Noname finally explained this strange word.

"It sounds like polymorphism is the ability to drive different cars that all use the same controls," I commented.
"This is exactly what I'm saying! Take a look at this code example," he said, displaying some text right in my mind.

The Car Interface

public interface ICar
{
    void Accelerate();
}

public class ElectricCar : ICar
{
    public void Accelerate()
    {
        Console.WriteLine($"Accelerating an electric car.");
    }
}

public class PetrolCar : ICar
{
    public void Accelerate()
    {
        Console.WriteLine($"Accelerating a petrol car.");
    }
}

public class Driver
{
    public static void Main()
    {
        ElectricCar electricCar = new ElectricCar();
        PetrolCar petrolCar = new PetrolCar();

        AccelerateCar(electricCar);
        AccelerateCar(petrolCar);
    }

    // Accelerates any car that implements ICar!!!
    private static void AccelerateCar(ICar car) // <-- Magic!
    {
        car.Accelerate();
    }
}

// Outputs:
// Accelerating an electric car.
// Accelerating a petrol car.

After giving me some time to look through the code, Noname asked, "Do you see the beauty of what is happening here?"
"I think so," I replied. "Let me try to sort this out myself. So you have an interface ICar , and two classes that implement it: ElectricCar and PetrolCar . Then, in the Main method, you create two objects: one of type ElectricCar and one of type PetrolCar . Then you pass those objects to the method AccelerateCar . Wait! This method takes ICar - how does it even compile?"

"All the magic happens in the Accelerate method," Noname replied. " Polymorphism gives you the ability to pass any class that implements the ICar interface to the method AccelerateCar . It is possible because the type of the argument car in the AccelerateCar method is an ICar , not a specific PetrolCar or ElectricCar ."
As always, Nonames first explanation was a bit too technical for me to grasp it all.

"Noname, does the object change when it has a different type inside of the method?" I asked.
" When the object electricCar, after being passed to the method as a parameter, has a type ICar , it is not changed, but the way the receiving side sees it does change. The object electricCar continues to have the type ElectricCar , and an object petrolCar will still be of type PetrolCar . The trick is that inside the method AccelerateCar , both of their types are ICar . And the best thing about it - you don't need to know the exact type. You operate via an interface, the same as you do with the real car. You accelerate by pressing the pedal!" Noname's voice sounded excited. Either he had discovered a newfound appreciation for cars, or polymorphism was one of his favorite subjects.
"Ok, I think I understand now," I answered.

Polymorphism in C Sharp

"Good! Then you'll have no problem solving these exercises," Noname said.

I finished the exercises in about five minutes. It seemed like enough information for me to be able to change the routing system of Commander's server, but Noname seemed unstoppable. He wanted to explain all the nitty-gritty details of polymorphism. He continued:
"The same technique applies if you replace interfaces with base classes. The passing side will pass an object of a child class, and the receiving method receives that as an object of a base class. Take a look at how it looks in the code:"

public class Car
{
    public virtual void Accelerate()
    {
        Console.WriteLine("Accelerating an unknown car");
    }
}

public class ElectricCar : Car
{
    public override void Accelerate()
    {
        Console.WriteLine("Accelerating an electric car.");
    }
}

public class PetrolCar : Car
{
    public override void Accelerate()
    {
        Console.WriteLine("Accelerating a petrol car.");
    }
}

public class Driver
{
    public static void Main()
    {
        ElectricCar electricCar = new ElectricCar();
        PetrolCar petrolCar = new PetrolCar();

        AccelerateCar(electricCar);
        AccelerateCar(petrolCar);
    }

    // Accelerates any car that derives from Car!!!
    private static void AccelerateCar(Car car)
    {
        car.Accelerate();
    }
}

// Outputs:
// Accelerating an electric car.
// Accelerating a petrol car.

Noname explained his code: "The first interesting thing about this code is the same as for the previous code snippet: the method AccelerateCar works with an argument car of type Car , not ElectricCar or PetrolCar ."

"How does it understand which implementation of the method Accelerate to use when it calls car.Accelerate() ?" I asked, wondering if I was missing something obvious.
"This is the second interesting fact regarding the last example I gave you. Polymorphism works in such a way that every object stores a reference to its original type. Object car inside the method AccelerateCar is treated as Car , but when you call a method Accelerate on it, the first thing it does is check what the original type of the object was. Then it calls the method from the corresponding implementation."

public class Driver
{
    public static void Main()
    {
        ElectricCar electricCar = new ElectricCar();
        PetrolCar petrolCar = new PetrolCar();
        Car car = new Car();

        AccelerateCar(electricCar);
        AccelerateCar(petrolCar);
        AccelerateCar(car);
    }

    private static void AccelerateCar(Car car)
    {
        car.Accelerate(); // Here C# checks the initial type of the object car
        // Then it finds the method "Accelerate" in that class and calls it
        // If there are no overrides of this method in the "real" class,
        // the implementation from the parent class is used
    }
}

"Noname, one more question: what does that last line of the comment mean? Can you give a code example?" I asked.
"Oh yes, sure, here you go!" he displayed an image with the next code snippet.

public class Car
{
    public virtual void Accelerate()
    {
        Console.WriteLine("Accelerating an unknown car");
    }
}

public class ElectricCar : Car
{
    // Nothing
}

public class Driver
{
    public static void Main()
    {
        ElectricCar electricCar = new ElectricCar();

        AccelerateCar(electricCar);
    }

    private static void AccelerateCar(Car car)
    {
        car.Accelerate(); // This one calls the method Accelerate from Car
        // Because ElectricCar does not override the method Accelerate
    }
}
// Outputs:
// Accelerating an unknown car

"Noname, maybe this is enough for the first time? My brain is melting from all of this polymorphism stuff."
"Okay, I understand. Humans are far from perfect; your brain is one of the weakest components of the body. Can you handle one last fact before we stop?" he asked.
"Fine," I replied, "but just one."

"In the previous examples, you saw how polymorphism works on methods. It can also be used on variables."
"Variables? Interesting!"
"First, we create a variable of type Car . Then, you can assign to it any object of types ElectricCar or PetrolCar because they both derive from Car . In general, you can assign an object of a child class to a variable of the parent class. Here's a code example:"

Car car1 = new Car();
Car car2 = new ElectricCar();
Car car3 = new PetrolCar();

"What is the point of doing so?" I asked.
"Well, there's no tangible point, but it provides a good example of polymorphism functionality. Having only those 3 lines is not enough to understand the usage of the variable car . Let's do something with it, let's say, call our favorite method Accelerate . Because of polymorphism, C# calls the right method for each class, as we saw in the method AccelerateCar in previous examples. "

public class Car
{
    public virtual void Accelerate()
    {
        Console.WriteLine("Accelerating an unknown car.");
    }
}

public class ElectricCar : Car
{
    public override void Accelerate()
    {
        Console.WriteLine("Accelerating an electric car.");
    }
}

public class PetrolCar : Car
{
    public override void Accelerate()
    {
        Console.WriteLine("Accelerating a petrol car.");
    }
}

public class Driver
{
    public static void Main()
    {
        Car car1 = new Car();
        Car car2 = new ElectricCar();
        Car car3 = new PetrolCar();

        car1.Accelerate(); // C# knows that it should call Accelerate from
                           // Car
        car2.Accelerate(); // C# knows that it should call Accelerate from
                           // ElectricCar
        car3.Accelerate(); // C# knows that it should call Accelerate from
                           // PetrolCar
    }
}

// Outputs:
// Accelerating an unknown car.
// Accelerating an electric car.
// Accelerating a petrol car.

"Thanks, Noname, that example helps. It's clear how to use polymorphism technically, but I still can't see any practical use for it," I said, hoping he had more to add.

Noname replied, "Ok, consider that you want to ask a user which car he or she wants to use. I'll write this code for you using interfaces this time:"

public interface ICar
{
    void Accelerate();
}

public class ElectricCar : ICar
{
    public void Accelerate()
    {
        Console.WriteLine("Accelerating an electric car.");
    }
}

public class PetrolCar : ICar
{
    public void Accelerate()
    {
        Console.WriteLine("Accelerating a petrol car.");
    }
}

public class Driver
{
    public static void Main()
    {
        ICar car; // Not assigning any object as we don't know which car to
                  // drive.

        if (Console.ReadLine() == "electrical")
        {
            car = new ElectricCar();
        }
        else
        {
            car = new PetrolCar();
        }

        car.Accelerate(); // C# knows which method to call: from
                          // ElectricCar or PetrolCar
    }
}

"At runtime, this code creates different objects that implement interface ICar . You can argue that it is possible to achieve the same result with ifs - and that is partially true - but using polymorphism provides significant advantages:"

  1. Decoupling. Using polymorphism, you can separate the implementation logic from the usage logic. You can create objects in one place and pass them as an implementation of some interface without specifying the actual type. This allows work to easily be separated between teams, with each team working independently but sharing a common interface.
  2. Extensibility. To add a new car to the code above you would need to add a new class, but you don't need to change the code in Main because the object car has the interface type ICar . The same principle applies to real cars: if a manufacturer creates a new car, they don't need to teach you to drive it. Instead, they set up their new car to use the common interface implemented in all cars.

I finally felt comfortable with polymorphism. Now, it was time to act.
"I think I'm good with this topic, Noname. It's time to replace Commander's communication module," I said.
"What exactly are you planning to do?" he asked.
"Be patient, you'll see everything in a moment," I replied. It was my turn to be mysterious.

"That was smart of you, Teo!" Noname exclaimed, "You tricked the machines; now they'll continue talking to Commander, and Commander will think it's talking to Wonderland, but in reality, it'll be talking to you. Moreover, that little change to forward your orders to Wonderland as if Commander sent them was genius! This all means... This means that... Teo, you are the new Commander. You control the entire resistance on Earth!" Noname's voice dropped an octave as he started to realize the situation.

I felt mighty. I had an uplifting feeling that finally, this one time, I'd be the one making the rules. I'd be giving the orders, and there would be no more secrets, because I'd be the source. I owned the biggest secret of Wonderland. Perhaps the biggest secret on the planet.

So, what to do now? What orders to give? Whom to love and whom to hate?
I figured I'd start slow and replace the pathetic, tasteless coffee sludge that they use here with proper coffee beans.
To be continued...

To be continued Codeasy

At Codeasy we believe that learning should be fun and engaging. Please reach out to us to share your feedback and collaboration ideas.