Skip to content

Latest commit

 

History

History
150 lines (109 loc) · 3.85 KB

File metadata and controls

150 lines (109 loc) · 3.85 KB

Abstract Structs

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 struct directly

  • Shared Fields:

    Fields defined in an abstract struct are inherited by all derived struct'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

Hierarchical Lifecycle

Constructors

Derived struct's are responsible for initializing their base fields using the : Base(...) syntax.

The Initialization Order:

  1. Allocation: A memory for instance is allocated
  2. Base Initialization: Base fields are assigned default values and the abstract struct constructor body executes
  3. 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;
    }
}

Memberwise Chaining

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);

Destructors

Destruction follows a Bottom-Up Cleanup logic (Derived-to-Base).

  1. Derived Cleanup: The ~Derived() destructor executes first
  2. Base Cleanup: The ~Base() destructor is called automatically immediately after

Functions Static Polymorphism

Hierarchical behavior is realized through abstract and override functions. This provides a familiar polymorphic interface without the cost of a virtual table.

Defining Abstract Functions

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...");
    }
}

Overriding Behavior

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

  • base Access: Use base.FunctionName() to call the implementation from the immediate parent struct

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...");
    }
}

Pattern Matching and Refinement

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
    }
}