Interfaces
An interface defines a behavioral contract without instance fields or a concrete object layout.
Declaring an interface
Interface members declare signatures without implementation bodies. Methods, properties and indexers can participate in a contract.
interface IRenderable
{
void Render();
bool readonly Visible { get; }
int readonly this[int index] { get; }
}
Interface members are public contract members. A readonly member can be called through a readonly interface reference.
Implementing an interface
Add the interface to a struct's base list and provide compatible public instance members for the complete contract.
struct Sprite : IRenderable
{
private bool visible;
public Sprite(bool isVisible)
{
visible = isVisible;
}
public void Render()
{
DrawSprite();
}
public bool readonly Visible
{
get { return visible; }
}
public int readonly this[int index]
{
get { return index; }
}
}
Interface references
A struct value converts to an interface value, and a struct reference converts to an interface reference. The interface representation preserves the concrete object and its dispatch information. Calls are routed to the implementation belonging to the runtime struct.
Sprite sprite = Sprite(true);
IRenderable view = sprite;
IRenderable& renderable = sprite;
view.Render();
bool visible = renderable.Visible;
An interface pointer type such as IRenderable* may be used directly, but a raw Sprite* is not implicitly converted to it. Form an interface value or reference before working through the interface contract.
Representation and value preservation
An interface value is a non-owning view of a concrete value, not a base-sized copy of it. Its runtime representation carries two pieces of information:
- a data pointer to the complete concrete value;
- a dispatch-map pointer for the implemented interface.
Creating or copying an interface value copies this small descriptor. It does not copy only the fields named by the interface, box the struct or discard fields declared by the concrete type.
Vector3 vector = Vector3(1.0f, 2.0f, 3.0f);
IVector view = vector;
view.Normalize(); // operates on the original Vector3, including Z
The concrete-only field Z is not directly visible through IVector, but it remains part of vector and is available to the concrete implementation invoked through the interface. Converting a non-addressable temporary first materializes the complete concrete temporary; it does not create an interface-sized slice.
An interface view created from a stack value does not move that value to the heap or extend its lifetime. Do not store or return the view where it can outlive the concrete source. When longer-lived storage is required, allocate the concrete value with new, form the view from the pointed-to value and call delete only after every view is no longer used.
This is the interface counterpart of the no object slicing rule for inheritance: changing the static view changes which members may be named, not which bytes exist in the concrete value.
Interface inheritance
An interface may extend multiple other interfaces. Its contract includes all inherited members together with its own declarations.
interface IUpdatable
{
void Update();
}
interface IEntity : IRenderable, IUpdatable
{
int GetId();
}
A struct may implement several interfaces while also extending one base struct.