virtual and override
Let's go back to the fleet. The dispatcher has one instruction for every vehicle: on command, say who you are. The base version of the instruction answers with "a vehicle". But each kind of vehicle may rewrite it for itself: a car answers with "a car", a truck with "a truck".
The dispatcher does not check which vehicle is on the line. One command goes to everyone, and each vehicle follows its own version of the instruction.
In C#, the dispatcher's role is played by code that works with a variable of the base type. A variable of type Vehicle can hold a Car object: every car is a vehicle. Which version of the method does a call through such a variable run?
Vehicle v = new Car();
Console.WriteLine(v.Who()); // car
class Vehicle
{
public virtual string Who() => "vehicle";
}
class Car : Vehicle
{
public override string Who() => "car";
}Let's walk through it line by line:
- In
Vehicle v = new Car();, the variable has typeVehicle, while the object it leads to has typeCar. virtualinVehiclelets derived classes rewrite the methodWho.overrideinCarrewrites it.v.Who()printscar: the call ran the object's version, even though the variable has typeVehicle.
The variable's type and the object's type
The call v.Who() involves two types, and each decides something different:
- the variable's type,
Vehicle, decides which methods you may call: the compiler checks thatWhoexists inVehicle; - the object's type,
Car, decides which version runs: that is settled at run time, when the object already exists.
A method marked virtual is called a virtual method, and its new version marked override in a derived class is an override. The ability of one and the same call to run different code for objects of different types is called polymorphism. Interviewers often ask about it: "What is polymorphism, and how does it work in C#?"
In the widget, one and the same variable v of type Vehicle leads to three objects in turn. → Car { } reads the same way as in the theme on classes: v leads to a Car object, and the empty braces mean it has no fields. Press "Next step" and watch the top frame: its header names whose version of Who is running.
1Vehicle v = new Vehicle();2Console.WriteLine(v.Who());3v = new Car();4Console.WriteLine(v.Who());5v = new Bus();6Console.WriteLine(v.Who());7 8class Vehicle9{10 public virtual string Who()11 {12 return "vehicle";13 }14}15 16class Car : Vehicle17{18 public override string Who()19 {20 return "car";21 }22}23 24class Bus : Vehicle { }
The variable v of type Vehicle leads to a Vehicle object.
The variable's type never changed during the program, yet different versions of Who ran. When a derived class does not override a virtual method, as with Bus, its objects run the version inherited from the base class.
Here the dispatcher analogy is off. In the fleet, any kind of vehicle may rewrite any instruction, while in C# you can override only a virtual method, one marked virtual in the base class.
Calling the base version: base
An override does not have to throw the base version away. Inside a method marked override, you can call it through base.:
Vehicle t = new Taxi();
Console.WriteLine(t.Who()); // taxi, a car
class Taxi : Car
{
public override string Who() => "taxi, a " + base.Who();
}This continues the lesson's first program: add the statements after its statements, and the class Taxi at the end of the file. Let's see what happens:
Taxiinherits fromCar, andCarfromVehicle. A method markedoverridestays virtual itself, soTaxican override the version ofCar.t.Who()runs the object's version, which isTaxi.Who.baserefers to the base class ofTaxi, which isCar. Sobase.Who()runs the version ofCarand returns"car".Taxi.Whoputs its own beginning in front of it, andtaxi, a caris printed.
Overriding ToString
Every class inherits from object, and object has a virtual method ToString. Console.WriteLine calls it when it prints an object. The version from object returns the name of the object's type. Override ToString, and Console.WriteLine prints whatever you set:
Console.WriteLine(v); // Car
var bike = new Bike("BMX");
Console.WriteLine(bike); // Bike BMX
class Bike : Vehicle
{
public string Model;
public Bike(string model) { Model = model; }
public override string ToString() => $"Bike {Model}";
}This fragment continues the first program too:
Console.WriteLine(v)printsCar.Carhas noToStringof its own, so the version fromobjectruns. Note that it printed the name of the object's type, not the variable's.BikeoverridesToString, soConsole.WriteLine(bike)printsBike BMX.
Common mistake
A method was overridden though the base class did not mark it virtual:
Vehicle v = new Car();
Console.WriteLine(v.Who());
class Vehicle
{
public string Who() => "vehicle";
}
class Car : Vehicle
{
public override string Who() => "car"; // ✗
}error CS0506: 'Car.Who()': cannot override inherited member 'Vehicle.Who()' because it is not marked virtual, abstract, or overrideThe program does not compile. In C#, a method is not virtual by default, and Vehicle did not mark Who with the word virtual, so it did not allow rewriting it. Add virtual to the declaration of Who in the class Vehicle, as in the first example.
Key points
virtualallows a derived class to rewrite a method, andoverriderewrites it.- The variable's type decides which methods you may call, and the object's type decides which version of a virtual method runs.
base.Who()inside an override calls the base class's version.Console.WriteLine(obj)callsToString; without an override it returns the name of the object's type.