Object-Oriented C# · Lesson 13

Virtual dispatch through a base reference

Extend a genuine shared contract with an overriding implementation.

How it works

A virtual member provides an implementation that a derived class can override. Calling that member through a base-type reference still selects the override associated with the runtime object. This is virtual dispatch: the caller depends on the base contract while the particular object supplies its specialized behavior.

Inheritance is useful when the derived type can honor the expectations of the base type. It should not be used merely to borrow a few methods from an unrelated class. State the shared contract first, including input rules and the meaning of the result, then verify that every derived implementation keeps that contract. An override can change the calculation without changing what callers are entitled to expect from it.

Walk through the example. ShippingFee supplies a standard fee calculation. ExpressFee overrides the method to add a surcharge after invoking the base calculation. Both objects are stored in an array whose declared element type is ShippingFee. Calling Calculate through those base references prints the standard result for one object and the express result for the other, without a type-test branch in the loop.

Watch the boundary. A new member that hides a base member is different from an override. Hiding can make behavior depend on the variable's declared type rather than the runtime type, which is usually surprising in a polymorphic design. Use override for the intended virtual contract, and avoid strengthening a derived method's preconditions so that valid base-class calls suddenly become invalid.

Practice independently. Model two kinds of delivery fee under one base contract and exercise both through a base-typed collection. Add boundary tests for zero weight and a representative positive weight. Then create a deliberately hidden method with new in a separate experiment and compare calls through base and derived variables. Explain why that experiment is unsuitable when callers expect consistent virtual dispatch.

A working example

using System;
using System.Collections.Generic;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

ShippingFee[] fees = { new ShippingFee(), new ExpressFee() };
foreach (ShippingFee fee in fees)
    Console.WriteLine(fee.Calculate(3).ToString("F2", CultureInfo.InvariantCulture));
class ShippingFee
{
    public virtual decimal Calculate(int units) => units * 2m;
}
sealed class ExpressFee : ShippingFee
{
    public override decimal Calculate(int units) => base.Calculate(units) + 5m;
}

Expected output

6.00
11.00
Try this
Use override for a shared virtual contract; hiding a member is a different dispatch rule.