An abstract struct serves as a template for shared data and behavior.
abstract struct's hierarchies are closed.
The compiler identifies all derived
struct's at compile-time to enable high-performance static dispatch.
-
Non-Instanceable:
You cannot create an instance of an
abstract structdirectly -
Shared Fields:
Fields defined in an
abstract structare inherited by all derivedstruct's and are guaranteed to be located at the same memory offset -
Static Dispatch:
Unlike traditional OOP,
abstract struct's does not use vtables. Polymorphic behavior is resolved via tag-based dispatch
Derived struct's are responsible for initializing their base fields using the : Base(...) syntax.
The Initialization Order:
- Allocation: A memory for instance is allocated
- Base Initialization: Base fields are assigned default values and the
abstract structconstructor body executes - Derived Initialization: Derived fields are assigned default values, and the derived constructor body executes
abstract struct Shape
{
public int X;
public int Y;
public Shape(int x, int y)
{
X = x;
Y = y;
}
}
struct Circle : Shape
{
public float Radius;
public Circle(int x, int y, float radius) : Shape(x, y)
{
Radius = radius;
}
}If no manual constructor is defined, the compiler-generated Memberwise Constructor automatically includes parameters for the base fields and chains them to the base constructor.
abstract struct Shape
{
public int X;
public int Y;
}
struct Circle : Shape
{
public float Radius;
}
var circle = new Circle(10, 20, 5.0f);Destruction follows a Bottom-Up Cleanup logic (Derived-to-Base).
- Derived Cleanup: The
~Derived()destructor executes first - Base Cleanup: The
~Base()destructor is called automatically immediately after
Hierarchical behavior is realized through abstract and override functions. This provides a familiar polymorphic interface without the cost of a virtual table.
To allow a function to be redefined in a derived struct, it must be marked with the abstract modifier.
abstract struct Shape
{
// can be overridden
public abstract void Draw()
{
Console.WriteLine("Drawing a shape...");
}
}Derived struct's use the override modifier to provide a specific implementation.
-
override: Redefines the function. This implementation is final for any further descendants unless marked otherwise -
abstract override: Redefines the function and allows further descendants to override it again -
baseAccess: Usebase.FunctionName()to call the implementation from the immediate parentstruct
struct Circle : Shape
{
// overrides 'Shape.Draw()' and remains abstract for children
public abstract override void Draw()
{
base.Draw(); // calls 'Shape.Draw()'
Console.WriteLine("Drawing a Circle...");
}
}
abstract struct FancyCircle : Circle
{
// final override, descendants of 'FancyCircle' cannot override this
public override void Draw()
{
base.Draw(); // calls 'Circle.Draw()'
Console.WriteLine("Adding fancy colors...");
}
}Because the hierarchy is closed, you can use the switch statement or expression for exhaustive pattern matching. Within a match, the Identity is automatically refined to its specific type.
void ProcessShape(Shape s)
{
switch (s)
{
case (.Circle) => Console.WriteLine(s.Radius); // Refined to Circle
case (.Rectangle) => Console.WriteLine(s.Width); // Refined to Rectangle
}
}