Let us properly comprehend the concepts of polymorphism, C# virtual, and override techniques. In this example, we have an abstract class called Car, which includes a protected property named Speed. It also contains an abstract method called Accelerate, which increases the speed by ten whenever invoked. Additionally, there is a non-virtual implemented method called Stop, which currently outputs the text ‘Stopping the car’ to the screen.
public abstract class Car
{
protected int speed;
public abstract void Run();
public virtual void Accelerate() => this.speed += 10;
public void Stop() => Console.WriteLine("Stopping the car");
}
Now, let’s consider the derived class Toyota. In this class, we override the Run method, as it is mandatory to override abstract methods. Failure to do so will result in a compiler error. In the Toyota class, we simply print the text ‘Toyota is running at speed: ‘ followed by the current speed value obtained from the Speed property.
public class Toyota : Car
{
public override void Run() => Console.WriteLine($"Toyota is running at speed: {this.speed}");
}
Next, we implement the Ferrari class.
public class Ferrari : Car
{
public override sealed void Run() => Console.WriteLine($"Ferrari is running at speed: {this.speed}");
public override void Accelerate() => this.speed += 50;
public new void Stop() => Console.WriteLine("Stopping Ferrari");
}
The Ferrari class is also derived from the Car class. It is obligatory to override the Run method, which outputs the text ‘Ferrari is running at speed’. The speed value is obtained from the Speed property. Notice that we can seal the Run method, indicating that it cannot be overridden by any class derived from Ferrari. The ‘override’ and ‘sealed’ keywords are used in the same method signature, which breaks polymorphism from that point onward. Additionally, the Accelerate method, which was marked as virtual, is overridden in the Ferrari class to increase the speed by 50. The Ferrari class also attempts to override the Stop method. However, since Stop is not a virtual method, it does not behave polymorphically. The compiler will issue a warning unless the ‘new’ keyword is used to clarify our intention. In this case, we print the text ‘Stopping Ferrari’.
Now, let’s examine how we use these classes in the Program class with a Main method.
class Program
{
static void Main(string[] args)
{
Car toyota = new Toyota();
toyota.Accelerate();
toyota.Run();
Car ferrari = new Ferrari();
ferrari.Accelerate();
ferrari.Run();
ferrari.Stop();
Ferrari realFerrari = (Ferrari)ferrari;
realFerrari.Stop();
}
}
In the first line, we create a Toyota instance. Note that the reference type is Car, while the object type created is Toyota. In this case, polymorphic methods marked as virtual only consider the actual type, regardless of the reference type. Although the reference type is Car, the actual type during runtime is Toyota. Therefore, when we invoke the Toyota Accelerate method, the method associated with the Toyota class is invoked. The same applies to the Run method.
Now, let’s observe the behavior with the Ferrari instance. The reference type for Ferrari is Car. However, since Accelerate is a virtual and polymorphic method, the method associated with the actual object type during runtime is called. Thus, in this case, the Accelerate method of the Ferrari class is invoked. Similarly, when we call the Ferrari Run method, which is another polymorphic method, the corresponding method in the Ferrari class is executed.
Now, let’s consider the Ferrari’s Stop method. Will it call the Stop method belonging to the Car class or the one defined in the Ferrari class?
Let’s revisit the classes. In the Car class, we have a non-virtual method called Stop. In the Ferrari class, we re-implemented the method using the ‘new’ keyword. In this scenario, since Stop is not a virtual method, the actual method called is determined by the reference type. Since our reference type is Car, the Stop method inside the Car class will be called.
However, if we cast the Ferrari instance to the Ferrari type, the real reference type will be Ferrari. In this case, calling the realFerrari’s Stop method will invoke the Stop method defined in the Ferrari class.
To summarize, in the first method, Toyota’s Accelerate method is called, increasing the Speed value by 10. When we call the Run method with Toyota, the text ‘Toyota is running at speed: 10’ is printed. The same applies to the Ferrari instance. We accelerate to 50, and when we call the Run method, it outputs the text ‘Ferrari is running at speed: 50’. When we call the Ferrari Stop method, it does not behave polymorphically because Stop is not a virtual method. Instead, the Stop method inside the Car class is executed. However, when we cast the type to Ferrari and call the Stop method, the actual type specified in the reference type, which is the Ferrari’s Stop method, is invoked.