Modern C# · Lesson 18

Virtual behavior through a base reference

Follow runtime dispatch while keeping the public contract stable.

How it works

A base reference can refer to an instance of a derived class. The messages array stores Message references, but its second element points to an Alert. Calling a virtual method through that reference uses the override belonging to the runtime object, which is why the second printed line is alert.

The base method establishes a common operation named Render. The derived class changes its implementation with override, rather than asking every caller to inspect a type name and write another conditional branch. A caller can process the collection through the base contract while each object supplies its own behavior.

This flexibility also creates a responsibility. An override should honor the meaning and expectations of the base operation. If callers expect Render to return a usable string without modifying the message, an override that changes unrelated global state or unexpectedly throws on an ordinary message would make the abstraction harder to trust.

Do not replace override with new and assume the behavior is identical. A new member hides another member and changes how member lookup works through different static types. Virtual dispatch is an explicit agreement across the inheritance hierarchy. Use Check to explore an accidental missing override modifier, then restore the intended declaration.

Practice by adding a Reminder subclass whose Render result includes a deadline supplied through its constructor. Keep the loop unchanged. Add one instance of each type and predict the full output before running. The unchanged loop demonstrates the practical value of a stable base contract.

Finally, compare inheritance with a strategy delegate or interface for the same task. A deep inheritance tree is not required merely because several objects share an operation. Prefer the smallest relationship that expresses the actual model, and keep runtime dispatch separate from unrelated questions about object ownership or value equality.

A working example

using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

Message[] messages = { new Message(), new Alert() };
foreach (var message in messages) Console.WriteLine(message.Render());
class Message { public virtual string Render() => "message"; }
class Alert : Message { public override string Render() => "alert"; }

Expected output

message
alert
Try this
The variable type is Message; the runtime object selects the override.