Documentation menu
Language

Classes & Interfaces

Serez Code has C#-style OOP — interfaces for pure data shapes, classes for data + behavior, and single inheritance with super().

Interfaces

An interface is a typed data record — fields only, no methods. Think of it as a strict struct:

interface Point {
    x: decimal,
    y: decimal,
}

let origin = new Point({ x: 0.0, y: 0.0 })
let p      = new Point({ x: 3.0, y: 4.0 })

out p.x   // → 3.0
p.x = 10.0
out p.x   // → 10.0

Classes

Classes bundle data and behavior. The constructor has the same name as the class and is marked public:

public class Animal {
    public Animal(string name, string sound) {
        this.name   = name
        this.sound  = sound
        this.energy = 100
    }

    public void speak() {
        out "{this.name} says: {this.sound}"
    }

    public void eat(int amount) {
        this.energy = this.energy + amount
    }

    public string describe() {
        return "{this.name} (energy: {this.energy})"
    }
}

let dog = new Animal("Rex", "Woof")
dog.speak()           // → Rex says: Woof
dog.eat(20)
out dog.describe()    // → Rex (energy: 120)

Inheritance

Use : ParentClass to inherit. Call super(...) first to pass arguments up to the parent constructor:

public class Dog : Animal {
    public Dog(string name, string breed) {
        super(name, "Woof")   // runs Animal's constructor
        this.breed = breed
    }

    public string getBreed() {
        return this.breed
    }

    // Override the parent method
    public string describe() {
        return "{this.name} [{this.breed}] (energy: {this.energy})"
    }
}

let fido = new Dog("Fido", "Labrador")
fido.speak()           // → Fido says: Woof  (inherited from Animal)
out fido.describe()    // → Fido [Labrador] (energy: 100)
out fido.getBreed()    // → Labrador
super() is implicit when it can be (core ≥ 9.17).If the parent constructor takes no required arguments, it runs on its own — a subclass with no constructor at all, or one that simply omits the call, still gets the parent's fields. The chain runs beforethe child's body, so the child can still overwrite what it inherits. You only have to write super(...) yourself when the parent needs arguments, as Dog does above.
public class Base {
    public Base() { this.n = 10 }
}

public class Child : Base { }          // no constructor at all
out (new Child()).n                    // → 10  — Base() ran on its own

public class Other : Base {
    public Other() { this.own = 5 }    // no super() call
}
let o = new Other()
out o.n                                // → 10  — inherited anyway
out o.own                              // → 5

Public & private methods

public class Counter {
    public Counter(int start) {
        this.value = start
    }

    private void increment() {
        this.value = this.value + 1
    }

    public int next() {
        this.increment()   // private method — ok from inside
        return this.value
    }
}

let c = new Counter(0)
out c.next()   // → 1
out c.next()   // → 2
// c.increment()  ❌ ERROR: cannot call private method from outside

Static methods

Static methods belong to the class itself, not to any instance. No this available:

class MathUtils {
    public static int square(int n) { return n * n }
    public static int max(int a, int b) {
        if (a > b) { return a }
        return b
    }
}

out MathUtils.square(5)    // → 25
out MathUtils.max(7, 3)    // → 7

Getters & setters

Computed properties that look like regular fields from the outside:

public class Temperature {
    public Temperature(decimal celsius) {
        this.celsius = celsius
    }

    public get decimal fahrenheit() {
        return this.celsius * 9.0 / 5.0 + 32.0
    }

    public set fahrenheit(decimal f) {
        this.celsius = (f - 32.0) * 5.0 / 9.0
    }
}

let t = new Temperature(0.0)
out t.fahrenheit      // → 32.0   (getter, no parentheses)
t.fahrenheit = 212.0  // setter
out t.celsius         // → 100.0

Method references

Writing obj.method without parentheses gives you the method as a value, bound to that object — it does not call it. Call it later and it still mutates the object it came from. This is what makes it possible to hand a handler around as data: store it in an array or a dictionary, pass it to another function, or give it to a UI component as a callback prop.

public class Counter {
    public Counter() { this.n = 0 }
    public void incr() { this.n = this.n + 1 }
    public void add(int k) { this.n = this.n + k }
}

let c = new Counter()

let bump = c.incr     // no parentheses: a reference, NOT a call
out c.n               // → 0    (nothing ran)
bump()
bump()
out c.n               // → 2    (mutates the original object)

let handlers = [c.incr, c.add]
handlers[1](10)
out c.n               // → 12

Resolution for a parenthesis-less obj.name is: a field first, then a getter (get name(), which does run on read), and only then a method reference. A bound reference keeps its class context, so its body can still reach the class's own private members — and referencing a private method from outside is rejected just like calling it would be.

Passing a handler to a component. This is the idiomatic way to let a child call back into its parent — the parent hands over the method, the child invokes it:
<TaskRow onPick={this.pick} />          // parent: a reference, not a call
<Button onClick={this.props.onPick}></Button>   // child: invokes it on click

Abstract classes

Abstract classes can't be instantiated directly — they define a contract for subclasses:

abstract class Shape {
    public Shape(string name) {
        this.name = name
    }

    // Subclasses must implement this
    public decimal area() {
        throw "area() not implemented in {this.name}"
        return 0.0
    }

    public string describe() {
        return "{this.name}: area={this.area()}"
    }
}

public class Circle : Shape {
    public Circle(decimal r) {
        super("Circle")
        this.r = r
    }
    public decimal area() { return 3.14159 * this.r * this.r }
}

let c = new Circle(5.0)
out c.describe()   // → Circle: area=78.53975

Sealed classes

A sealed class can't be subclassed — useful for value types you want to lock down:

sealed class Token {
    public Token(string kind, string value) {
        this.kind  = kind
        this.value = value
    }
}

// public class MyToken : Token { }  ❌ ERROR: Cannot inherit from sealed class 'Token'

Enums

enum Direction { North, South, East, West }
enum Status    { Ok, Error, Pending }

let dir = Direction.North
out dir   // → North

if (dir == Direction.North) {
    out "Heading north!"
}

fn string describe(any s) {
    switch (s) {
        case Status.Ok:      { return "All good" }
        case Status.Error:   { return "Something failed" }
        case Status.Pending: { return "Still waiting" }
        default:             { return "Unknown" }
    }
}

out describe(Status.Ok)   // → All good