"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.
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.
"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:"
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...