Abstraction and virtual dispatch
virtual, abstract and override let a base struct define behavior that derived structs specialize through dynamic dispatch.
Virtual methods
A virtual method provides a base implementation and reserves a dispatch slot that a derived struct may replace.
struct Entity
{
public virtual int Score()
{
return 0;
}
}
Overrides
A derived member uses override to replace a compatible inherited virtual or abstract member. Its signature must match the inherited declaration.
struct Enemy : Entity
{
public override int Score()
{
return 42;
}
}
The explicit modifier keeps replacement intentional: a declaration matching an inherited virtual member must say override.
Dynamic dispatch
Calls through a base reference or pointer select the override associated with the runtime struct.
Enemy enemy = Enemy { };
Entity& entity = enemy;
int score = entity.Score(); // calls Enemy.Score
The compiler maintains stable virtual slots across the inheritance chain so the same base operation resolves to the correct derived implementation.
Runtime interface dispatch
Converting a struct to an interface selects the implementation map from the runtime object, including when the conversion goes through a base pointer or reference. The source's static type must implement or inherit the interface contract; an interface implemented only by a descendant is not available through a non-implementing base.
interface IReset { int Reset(); }
struct Base : IReset
{
public virtual int Reset() { return 1; }
}
struct Derived : Base
{
public override int Reset() { return 42; }
}
int RuntimeInterface()
{
Derived value = Derived();
Base* pointer = &value;
IReset view = *pointer;
return view.Reset(); // Derived.Reset: 42
}
The same rule applies to interface properties and indexers, and to inherited interface implementations. Default local values, static storage and array elements initialize their dispatch headers. Readonly analysis follows the same runtime target rules.
This also applies to interface implementations that are ordinary, non-virtual methods: the runtime map selects the derived implementation. Dispatch storage belongs to the first polymorphic declaration, not to its non-polymorphic ancestors, and need not be at offset zero. Adding a polymorphic descendant leaves the base layout unchanged.
Abstract members
An abstract member declares required behavior without a body. A struct with an unresolved abstract member is abstract and cannot be instantiated. A derived struct becomes concrete after providing every required override.
struct Entity
{
public abstract int Score();
}
struct Enemy : Entity
{
public override int Score()
{
return 42;
}
}
Abstractness follows from unresolved abstract members; it does not require a separate modifier on the struct declaration.
Properties and indexers
Properties and indexers participate in the same virtual model. Abstract accessors end with semicolons, while an override supplies accessor bodies.
struct Source
{
public abstract int Value { get; }
}
struct FixedSource : Source
{
public override int Value
{
get { return 42; }
}
}
Virtual cleanup
A base struct may declare a virtual destructor so that delete through a base pointer selects the derived cleanup body. free performs no virtual dispatch because it releases storage only. Destructor slot replacement and base chaining are described in Destructors.