# TSL 对象与类

文档类型：语法主线
是否可直接用于生成代码：仅部分
是否含可直接照写示例：是
是否含不可照写反例：是
遇到不确定时：先按本页候选页继续判断；[02_core_model.md](02_core_model.md)（优先）、[09_units_and_scope.md](09_units_and_scope.md)、[20_object_runtime_and_introspection.md](20_object_runtime_and_introspection.md)、[21_builtin_runtime_objects.md](21_builtin_runtime_objects.md)、[24_object_overloads_and_iteration.md](24_object_overloads_and_iteration.md)、[11_pitfalls.md](11_pitfalls.md)；仍不命中时回到语法路由中心 [index.md](index.md)；如果问题已经超出语法层，回到 TSL 总入口 [../index.md](../index.md)

这一篇收拢面向对象语法，只保留语言层规则。

## 本篇职责

回答“`type Name = class`、字段、`static`、方法、`property`、析构、类类型、继承和对象创建在 TSL 里怎样写”。

本页后半段有少量依赖 `unit` 的双文件示例；如果你还没建立 `unit` / `uses` 的多文件心智模型，先看 [09_units_and_scope.md](09_units_and_scope.md)。

## 智能体对象/类判断流程

1. 先判断要写普通对象、继承、构造函数、类方法、静态字段还是运行时创建。
2. 对象声明优先使用本页明确的 `type Name = class ... end;` 骨架。
3. 普通本地类创建默认用 `new ClassName()`；只有需要字符串类名、类类型变量或跨 `unit` 路径时，才用 `createObject(...)`。
4. 类方法和静态字段优先用 `class(Name).Member` 或文档明确反射入口，不要裸写类名调用。
5. 构造函数默认保持 `public create`，不要把 `private` / `protected create` 当成会自动执行的构造函数。
6. 普通类成员默认显式写 `public` 段，不依赖隐式 public。
7. 方法体内访问当前实例成员默认直接写成员名；成员读写为了性能不加 `self` 前缀。
8. 命中对象反射 / 运行时状态、内置运行时对象、对象重载 / 迭代时，分别跳到 [20_object_runtime_and_introspection.md](20_object_runtime_and_introspection.md)、[21_builtin_runtime_objects.md](21_builtin_runtime_objects.md)、[24_object_overloads_and_iteration.md](24_object_overloads_and_iteration.md)。
9. 没有文档事实时不要发明对象/类写法。

## 核心规则

- 类定义统一按 `type Name = class ... end;` 写。
- 顶层类声明可以放在松散语句之后（单向允许）。
- 顶层类声明也可以放在顶层 `function / procedure` 之后。
- 类声明不要写在函数体内部。
- 在松散语句脚本里，可以先写松散语句再进入顶层类声明；但一旦进入顶层 `type Name = class ... end;` 声明，后面不能再回到松散语句模式。
- 类里可以直接写字段和方法。
- 字段可以写类型注解，例如 `name_: string;`。
- 类方法参数和返回值可以写类型注解，例如 `function ReadName(): string;`。
- 类的声明和实现可以分离：类内可以只声明方法签名，再用 `function ClassName.Method(...)` 在类外实现；带类型和 `overload` 时，类外实现保持同一组签名；类外实现属于声明区，后面不要再追加脚本语句。
- `static` 字段可用；可以通过类类型访问，也可以通过实例访问。
- `const` 成员可用；实例常量可通过实例读取，`static const` 可通过类类型读取，也可用于成员函数默认参数。
- 方法体里默认直接用成员名访问当前实例成员；普通成员读写为了性能不加 `self` 前缀，也不要为了“显式”访问而补这个前缀。
- `overload` 方法可用；同名不同参数个数的方法可以共存。
- 基础 `property` 形态：`property Name read fieldOrMethod write fieldOrMethod`。
- `property Name: Type ...` 这种类型注解写法可用。
- 参数化 `property` 可用；本页只写基础模式。
- 参数化 `property` 的 accessor 方法记录两种常见模式：读方法接同参数个数，写方法接“参数个数 + 赋值值”。
- 索引型 `property` 可用；调用时用圆括号 `obj.Prop(index)`。
- 固定 `index` property 可用；它可以把某个固定索引直接映射成普通属性读写。
- `class(Name)` 和 `findClass("Name")` 都可以拿到类类型。
- `findClass("ClassName", obj)` 可把对象转成指定父类视图。
- `class function` 可用；本页里的“类方法”就是指 `class function`，前者是中文描述，后者是代码写法。
- 类方法默认先拿到类类型，再在类类型上调用。
- 基础继承写法是 `type Child = class(Parent)`。
- 多重继承写法 `type Child = class(Base1, Base2)` 可用。
- 基础覆盖写法是：父类方法声明为 `virtual`，子类对应方法声明为 `override`。
- 基础祖先类调用：可以用 `Inherited;`、`Inherited MethodName(...)` 或 `class(BaseClass, ObjectName).MethodName()`。
- 创建对象有两种方式：`new ClassName()` 最常用，`createObject(...)` 作为次选。
- 普通本地类实例化默认生成 `new ClassName()`；`createObject("ClassName")`、`createObject(ClassType)` 只在字符串类名、类类型变量或跨 `unit` 路径场景生成。
- 如果类里定义了 `function create(...)`，`new`、`createObject("ClassName", ...)` 和 `createObject(ClassType, ...)` 都可以透传构造参数，也都支持默认参数和命名参数。
- 析构写法是无参 `function destroy();`；把对象引用设为 `nil` 时会触发它。
- 工厂式 `self(0)` / `self(1)` 可用；不要生成 `self()` 这种无参工厂式写法。
- 跨 `unit` 类路径创建和继承属于多文件边界；先回看 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)，不要从普通单文件 `new` / `createObject` 规则直接泛化。
- `{$ifdef parentClassInUnit}` 为真，可用于探测“继承和构造单元中的类”能力是否可用。
- `private` / `protected` / `public` 可用；类开头未写可见性时，成员默认按 `public` 处理，但生成代码默认显式写 `public`。
- 同一个可见性段里后续没有切换关键字的成员，会沿用前一个可见性。
- 外部访问 `private` / `protected` 字段或方法，会在执行时报对象成员访问错误。
- 子类可以直接访问父类 `protected` 成员；不能直接访问父类 `private` 成员。
- `create` 写成 `private` 或 `protected` 时，对象仍能创建，但 `new` 和 `createObject(...)` 不会执行该构造逻辑；构造函数应保持 `public`。
- 更复杂的多重继承边界和更复杂的对象模型不在本页展开。

## 可直接照写示例

使用这些示例时遵守：

- 普通本地类创建优先复制 `new ClassName()` 形态。
- `createObject(...)` 示例只在字符串类名、类类型变量或跨 `unit` 路径场景复制。
- `property` 类型注解只有在已有类型名证据时生成；不要为了完整性发明说明性类型名。

### 最小类与声明位置

最短类骨架：

如果任务只是要“先写出一个类”，先从这个骨架起手；后面的例子再逐步进入字段、工厂函数和声明位置边界。

代码块身份：可直接照写示例

```tsl
type Person = class
end;
```

字段、方法和 `new`：

代码块身份：可直接照写示例

```tsl
type Person = class
public
    name;
    function SetName(new_name);
    begin
        name := new_name;
    end;
end;

function MakePerson();
begin
    return new Person();
end;
```

类声明位置：

代码块身份：可直接照写示例

```tsl
obj := new MyClass();
obj.value := 5;
writeLn(obj.value);

type MyClass = class
public
    value;
end;
```

输出说明：

- 输出 `5`
- 说明顶层类声明可以放在松散语句之后
- 同时也说明 `new MyClass()` 可以解析到后面才出现的类声明

代码块身份：输出片段

```text
5
```

顶层函数后面也可以继续声明类：

代码块身份：可直接照写示例

```tsl
function MakeObj();
begin
    return 1;
end;

type MyClass = class
public
    value;
end;
```

结果说明：

- 上述声明顺序可以编译通过

类声明位置的反例：

代码块身份：反例 / 不可照写

```text
// 函数体内部声明类
function Demo();
begin
    type InnerClass = class
        value;
    end;
    return 1;
end;

// 松散语句在 type 之后继续出现
a := 1;
type MyClass = class
public
    value;
end;
writeLn(a);
```

结果说明：

- 函数体内部声明类会报 `invalid statement`
- 在松散语句脚本里，`type MyClass = class ... end;` 之后继续写 `writeLn(a);` 也会报 `invalid statement`

### 字段、静态成员、常量与可见性

`static` 字段：

代码块身份：可直接照写示例

```tsl
class(Human).count := 100;
writeLn(class(Human).count);
h := new Human();
writeLn(class(Human).count);
writeLn(h.count);

type Human = class
public
    static count;
    function create();
    begin
        count := (count ?: 0) + 1;
    end;
end;
```

输出说明：

- `class(Human).count := 100` 可以直接写静态字段
- `class(Human).count` 先输出 `100`
- 创建对象后，`class(Human).count` 输出 `101`
- 通过实例读取 `h.count` 也输出 `101`
- 单个静态字段默认写成 `static count;`

`const` 成员：

代码块身份：可直接照写示例

```tsl
o := new C();
writeLn(o.TestConst());
writeLn(o.TestConstInParam());
writeLn(o.a);
writeLn(class(C).b);

type C = class
public
    const a = 1;
    static const b = a + 10;
    function TestConst();
    begin
        return a + b;
    end;
    function TestConstInParam(param_b = b);
    begin
        return param_b;
    end;
end;
```

输出说明：

- 依次输出 `12`、`11`、`1`、`11`
- 说明类里可以定义实例常量和 `static const`
- 说明实例常量可通过实例读取，`static const` 可通过 `class(C)` 读取
- 说明成员常量也可用于成员函数默认参数

成员方法内直接访问成员：

代码块身份：可直接照写示例

```tsl
c := new Counter(10);
writeLn(c.Inc());

type Counter = class
public
    value;
    function create(v);
    begin
        value := v;
    end;
    function Inc();
    begin
        value := value + 1;
        return value;
    end;
end;
```

输出说明：

- `c.Inc()` 输出 `11`

可见性 `private` / `protected` / `public`：

代码块身份：可直接照写示例

```tsl
a := new A();
a.value := 9;
writeLn(a.value);
writeLn(a.ReadSecret());

type A = class
public
    value;
private
    secret;
    backup;
public
    function create();
    begin
        secret := 2;
        backup := 3;
    end;
    function ReadSecret();
    begin
        return secret + backup;
    end;
end;
```

输出说明：

- `value` 写在显式 `public` 段里，因此 `a.value := 9` 可以直接写。
- `backup` 跟在 `private` 段后面，没有重新切换关键字，因此仍按 `private` 处理。
- 上述例子依次输出 `9`、`5`。

`protected` 可供子类直接访问：

代码块身份：可直接照写示例

```tsl
b := new B();
b.SetValue(66);
writeLn(b.GetValue());
writeLn(b.TryCall());

type A = class
protected
    value;
    function Hidden();
    begin
        return 88;
    end;
end;
type B = class(A)
public
    function SetValue(v);
    begin
        value := v;
    end;
    function GetValue();
    begin
        return value;
    end;
    function TryCall();
    begin
        return Hidden();
    end;
end;
```

输出说明：

- 子类里可以直接访问父类 `protected` 字段。
- 子类里也可以直接调用父类 `protected` 方法。
- 上述例子依次输出 `66`、`88`。

`private` 的失败场景，这里用 `text` 展示反例：

代码块身份：反例 / 不可照写

```text
// 外部访问 private 字段
a := new A();
writeLn(a.secret);

type A = class
private
    secret;
public
    function create();
    begin
        secret := 7;
    end;
end;

// 子类调用父类 private 方法
b := new B();
writeLn(b.TryCall());

type A = class
private
    function Hidden();
    begin
        return 77;
    end;
end;
type B = class(A)
public
    function TryCall();
    begin
        return Hidden();
    end;
end;
```

输出说明：

- 外部访问 `a.secret` 会在执行时报对象成员访问错误。
- 子类里直接调用父类 `private` 方法也会在执行时报错。
- `private` / `protected` 方法访问也遵循同样边界：外部不能调 `private` / `protected` 方法，子类只能调 `protected` 方法，不能调 `private` 方法。

### 构造函数边界

`create` 建议保持 `public`：

代码块身份：可直接照写示例

```tsl
a := new A(111);
writeLn(a.value);

type A = class
public
    value;
private
    function create(v);
    begin
        value := v;
    end;
end;
```

输出说明：

- `a := new A(111)` 可以创建对象。
- 上述例子里的 `a.value` 输出 `<NIL>`，说明 `private create` 没有执行。
- `protected create`、`createObject("A", ...)` 和 `createObject(class(A), ...)` 也按同一规则处理；构造函数应保持 `public`。

### 属性、类型注解与类外实现

基础 `property`：

代码块身份：可直接照写示例

```tsl
box := new Box();
box.Value := 7;
writeLn(box.Value);
box.Value := -1;
writeLn(box.Value);

type Box = class
public
    value_;
    function SetValue(v);
    begin
        if v > 0 then
            value_ := v;
    end;
    property Value read value_ write SetValue;
end;
```

输出说明：

- `box.Value := 7` 后，`box.Value` 输出 `7`
- `box.Value := -1` 后，`box.Value` 仍输出 `7`

带类型注解的 `property`：

代码块身份：可直接照写示例

```tsl
box := new Box();
box.Value := 9;
writeLn(box.Value);

type Box = class
public
    value_;
    function SetValue(v);
    begin
        value_ := v;
    end;
    property Value: integer read value_ write SetValue;
end;
```

输出说明：

- `property Value: integer ...` 这种类型注解写法可以通过
- 上述例子中的 `box.Value` 输出 `9`

字段和类方法类型注解：

代码块身份：可直接照写示例

```tsl
box := new TypedBox("abc", 7);
writeLn(box.Name);
writeLn(box.Value);
writeLn(box.ReadName());

type TypedBox = class
public
    function create(name: string; value: any);
    begin
        name_ := name;
        value_ := value;
    end;
    function ReadName(): string;
    begin
        return name_;
    end;
    property Name: string read name_ write name_;
    property Value: any read value_ write value_;
private
    name_: string;
    value_: any;
end;
```

输出说明：

- `name_: string;` 和 `value_: any;` 可以作为类字段类型注解。
- `function create(name: string; value: any);` 可以作为类方法参数类型注解。
- `function ReadName(): string;` 可以作为类方法返回值类型注解。
- 上述例子依次输出 `abc`、`7`、`abc`。

代码块身份：输出片段

```text
abc
7
abc
```

类内声明、类外实现的带类型重载方法：

代码块身份：可直接照写示例

```tsl
a := new PairBox("left");
writeLn(a.Left);
writeLn(a.Right);
b := new PairBox("left", 2);
writeLn(b.Left);
writeLn(b.Right);
writeLn(b.ReadLeft());

type PairBox = class
public
    function create(left: string); overload;
    function create(left: string; right: any); overload;
    function ReadLeft(): string;
    property Left: string read left_ write left_;
    property Right: any read right_ write right_;
private
    left_: string;
    right_: any;
end;

function PairBox.create(left: string); overload;
begin
    create(left, nil);
end;

function PairBox.create(left: string; right: any); overload;
begin
    left_ := left;
    right_ := right;
end;

function PairBox.ReadLeft(): string;
begin
    return left_;
end;
```

输出说明：

- 类内可以只声明带类型的重载方法签名。
- 类外实现写成 `function PairBox.create(...); overload;`，并保持同样的参数类型和 `overload` 标记。
- 类外实现属于声明区；写完后不要再追加脚本语句。
- 从一个构造函数转调另一个构造函数时，直接写 `create(left, nil);`，不要加 `self` 前缀。
- 上述例子依次输出 `left`、`<NIL>`、`left`、`2`、`left`。

代码块身份：输出片段

```text
left
<NIL>
left
2
left
```

索引型 `property`：

代码块身份：可直接照写示例

```tsl
obj := new A();
obj.idx(0) := "abc";
writeLn(obj.idx(0));

type A = class
public
    arr;
    function create();
    begin
        arr := array();
    end;
    function rIndex(i);
    begin
        return arr[i];
    end;
    function wIndex(i, value);
    begin
        arr[i] := value;
    end;
    property idx read rIndex write wIndex;
end;
```

输出说明：

- `obj.idx(0) := "abc"` 可以写入索引 property
- `obj.idx(0)` 输出 `abc`

固定 `index` property：

代码块身份：可直接照写示例

```tsl
obj := new A();
obj.idx0 := "abc";
writeLn(obj.idx0);
writeLn(obj.idx(0));

type A = class
public
    arr;
    function create();
    begin
        arr := array();
    end;
    function rIndex(i);
    begin
        return arr[i];
    end;
    function wIndex(i, value);
    begin
        arr[i] := value;
    end;
    property idx read rIndex write wIndex;
    property idx0 index 0 read rIndex write wIndex;
end;
```

输出说明：

- `obj.idx0 := "abc"` 可以写入固定整数索引 property
- `obj.idx0` 输出 `abc`
- `obj.idx(0)` 也输出 `abc`

固定字符串索引：

代码块身份：可直接照写示例

```tsl
obj := new A();
obj.school := "math";
writeLn(obj.school);
writeLn(obj.idx("High school"));

type A = class
public
    arr;
    function create();
    begin
        arr := array();
    end;
    function rIndex(i);
    begin
        return arr[i];
    end;
    function wIndex(i, value);
    begin
        arr[i] := value;
    end;
    property idx read rIndex write wIndex;
    property school index "High school" read rIndex write wIndex;
end;
```

输出说明：

- `property school index "High school"` 这种固定字符串索引写法可以通过
- `obj.school` 输出 `math`
- `obj.idx("High school")` 也输出 `math`

参数化 `property`：

代码块身份：可直接照写示例

```tsl
d := new MyDate();
d.DateV(2025, 8) := 10;
writeLn(d.DateV());

type MyDate = class
public
    year_;
    month_;
    day_;
    function getDateV();
    begin
        return year_ * 10000 + month_ * 100 + day_;
    end;
    function setDateV(y, m, d);
    begin
        year_ := y;
        month_ := m;
        day_ := d;
    end;
    property DateV(y, m) read getDateV write setDateV;
end;
```

输出说明：

- `d.DateV(2025, 8) := 10` 可以给参数化 property 赋值
- `d.DateV()` 输出 `20250810`

参数化 `property` 的 accessor 方法参数：

代码块身份：可直接照写示例

```tsl
obj := new A();
obj.Item(2) := "x";
writeLn(obj.Item(2));

type A = class
public
    arr;
    function create();
    begin
        arr := array();
    end;
    function getItem(i);
    begin
        return arr[i];
    end;
    function setItem(i, value);
    begin
        arr[i] := value;
    end;
    property Item(i): string read getItem write setItem;
end;
```

输出说明：

- `read getItem` 这种”读方法接同参数个数”的写法可以通过
- `write setItem` 这种”写方法接参数个数 + 赋值值”的写法可以通过
- 上述例子中的 `obj.Item(2)` 输出 `x`

### 对象创建与类类型

`new` 关键字：

代码块身份：可直接照写示例

```tsl
type Person = class
end;

function MakePerson();
begin
    return new Person();
end;
```

通过类类型创建对象：

代码块身份：可直接照写示例

```tsl
cls := findClass("Person");
obj := createObject(cls);
obj.value := 42;
writeLn(obj.value);

type Person = class
public
    value;
end;
```

输出说明：

- `createObject(cls)` 可以通过类类型创建对象
- 上述例子中的 `obj.value` 输出 `42`

也可以用 `class(Name)` 取得类类型：

代码块身份：可直接照写示例

```tsl
cls := class(Person);
obj := createObject(cls, 44);
writeLn(obj.value);

type Person = class
public
    value;
    function create(v);
    begin
        value := v;
    end;
end;
```

输出说明：

- `class(Person)` 可以拿到 `Person` 的类类型
- `createObject(cls, 44)` 输出 `44`

构造参数、默认值和命名参数：

代码块身份：可直接照写示例

```tsl
a := new Person(22, c:99);
writeLn(a.x);
writeLn(a.y);
writeLn(a.z);
b := createObject("Person", 11);
writeLn(b.x);
writeLn(b.y);
writeLn(b.z);

type Person = class
public
    x;
    y;
    z;
    function create(a, b = 20, c = 30);
    begin
        x := a;
        y := b;
        z := c;
    end;
end;
```

输出说明：

- `new Person(22, c:99)` 依次输出 `22`、`20`、`99`
- `createObject("Person", 11)` 依次输出 `11`、`20`、`30`
- 说明构造函数默认参数和命名参数不只适用于普通函数调用，也适用于对象创建

类类型也同样支持默认参数和命名参数：

代码块身份：可直接照写示例

```tsl
cls := findClass("Person");
obj := createObject(cls, 33, c:77);
writeLn(obj.x);
writeLn(obj.y);
writeLn(obj.z);

type Person = class
public
    x;
    y;
    z;
    function create(a, b = 20, c = 30);
    begin
        x := a;
        y := b;
        z := c;
    end;
end;
```

输出说明：

- `createObject(cls, 33, c:77)` 依次输出 `33`、`20`、`77`
- 因此类类型入口下的 `createObject(cls, ...)` 也遵循同样的构造参数规则

`class function`：

代码块身份：可直接照写示例

```tsl
cls1 := class(MathBox);
writeLn(cls1.Add(3, 4));
cls2 := findClass("MathBox");
writeLn(cls2.Add(5, 6));

type MathBox = class
public
    class function Add(x, y);
    begin
        return x + y;
    end;
end;
```

输出说明：

- `class(MathBox).Add(...)` 可以调用类方法
- `findClass("MathBox").Add(...)` 可以调用类方法
- 上述例子依次输出 `7`、`11`

### 类方法、重载、继承与析构

`overload` 方法：

代码块身份：可直接照写示例

```tsl
t := new TestClass();
writeLn(t.fun(1, 2));
writeLn(t.fun(5));

type TestClass = class
public
    function fun(p1, p2); overload;
    begin
        return p1 + p2;
    end;
    function fun(param): integer; overload;
    begin
        return param + 10;
    end;
end;
```

输出说明：

- 依次输出 `3`、`15`
- 说明同名不同参数个数的方法可以共存

基础继承：

代码块身份：可直接照写示例

```tsl
d := new Dog();
writeLn(d.Speak());

type Animal = class
public
    function Speak();
    begin
        return 1;
    end;
end;
type Dog = class(Animal)
end;
```

输出说明：

- `type Dog = class(Animal)` 可以继承父类实例方法
- 上述例子中的 `d.Speak()` 输出 `1`

多重继承：

代码块身份：可直接照写示例

```tsl
obj := new C();
writeLn(obj.Fa());
writeLn(obj.Fb());

type A = class
public
    function Fa();
    begin
        return 1;
    end;
end;
type B = class
public
    function Fb();
    begin
        return 2;
    end;
end;
type C = class(A, B)
end;
```

输出说明：

- `type C = class(A, B)` 可以同时继承 `A` 和 `B` 的方法
- 上述例子依次输出 `1`、`2`

多重继承下的同名方法优先级：

代码块身份：可直接照写示例

```tsl
obj := new C();
writeLn(obj.Speak());

type A = class
public
    function Speak();
    begin
        return 1;
    end;
end;
type B = class
public
    function Speak();
    begin
        return 2;
    end;
end;
type C = class(A, B)
end;
```

输出说明：

- 当多个父类存在同名方法时，本例优先命中第一个父类 `A`
- 上述例子中的 `obj.Speak()` 输出 `1`

基础 `virtual` / `override`：

代码块身份：可直接照写示例

```tsl
d := new Dog();
writeLn(d.Speak());

type Animal = class
public
    function Speak(); virtual;
    begin
        return 1;
    end;
end;
type Dog = class(Animal)
public
    function Speak(); override;
    begin
        return 2;
    end;
end;
```

输出说明：

- 父类 `virtual` + 子类 `override` 组合可以通过
- 上述例子中的 `d.Speak()` 输出 `2`

子类同名方法不加 `override` 时是「隐藏（hide）」而不是「覆盖（override）」：

代码块身份：可直接照写示例

```tsl
c := new Child();
writeLn(c.Who());
writeLn(c.Ask());

type Parent = class
public
    function Who();
    begin
        return "parent";
    end;
    function Ask();
    begin
        return Who();
    end;
end;
type Child = class(Parent)
public
    function Who();
    begin
        return "child";
    end;
end;
```

输出说明：

- `c.Who()` 输出 `child`：直接调用命中子类自己的同名方法
- `c.Ask()` 输出 `parent`：父类方法内部的 `Who()` 仍解析到父类版本，说明不加 `override` 只是「隐藏」父类方法，没有改变父类内部的调用目标
- 要让父类方法内部也定向到子类实现，父类方法须声明 `virtual`、子类方法须声明 `override`

对照：父类 `virtual` + 子类 `override` 时，父类方法内部调用会定向到子类：

代码块身份：可直接照写示例

```tsl
c := new Child();
writeLn(c.Ask());

type Parent = class
public
    function Who(); virtual;
    begin
        return "parent";
    end;
    function Ask();
    begin
        return Who();
    end;
end;
type Child = class(Parent)
public
    function Who(); override;
    begin
        return "child";
    end;
end;
```

输出说明：

- `c.Ask()` 输出 `child`：`virtual` + `override` 后，父类方法内部的 `Who()` 定向到子类实现
- 这就是 hide 与 override 的关键区别：hide 只影响直接调用，override 改变了所有经由基类的间接调用

`class(BaseClass, ObjectName).MethodName()`：

代码块身份：可直接照写示例

```tsl
obj := new B();
writeLn(obj.Speak());
writeLn(class(A, obj).Speak());

type A = class
public
    function Speak(); virtual;
    begin
        return 1;
    end;
end;
type B = class(A)
public
    function Speak(); override;
    begin
        return 2;
    end;
end;
```

输出说明：

- `obj.Speak()` 输出 `2`
- `class(A, obj).Speak()` 会定向调用祖先类方法，因此输出 `1`

`findClass("ClassName", obj)` 强制转型：

代码块身份：可直接照写示例

```tsl
objb := new ClassB();
obj := findClass("ClassA", objb);
writeLn(obj.Fuc());
writeLn(obj is class(ClassA));
writeLn(obj is class(ClassB));

type ClassA = class
public
    function Fuc(); virtual;
    begin
        return 1;
    end;
end;
type ClassB = class(ClassA)
public
    function Fuc(); override;
    begin
        return 2;
    end;
end;
```

输出说明：

- `findClass("ClassA", objb)` 会把 `objb` 转成 `ClassA` 视图
- 上述例子中的 `obj.Fuc()` 输出 `1`
- 转型后的 `obj is class(ClassA)` 输出 `1`
- 转型后的 `obj is class(ClassB)` 输出 `0`

`Inherited;`：

代码块身份：可直接照写示例

```tsl
obj := new B();
obj.Speak();

type A = class
public
    function Speak(); virtual;
    begin
        writeLn(1);
    end;
end;
type B = class(A)
public
    function Speak(); override;
    begin
        Inherited;
        writeLn(2);
    end;
end;
```

输出说明：

- `Inherited;` 会先执行父类同名同参数方法
- 上述例子依次输出 `1`、`2`

`Inherited MethodName(...)`：

代码块身份：可直接照写示例

```tsl
obj := new B();
obj.Run();

type A = class
public
    function BaseValue(x);
    begin
        writeLn(x + 1);
    end;
end;
type B = class(A)
public
    function Run();
    begin
        Inherited BaseValue(5);
        writeLn(9);
    end;
end;
```

输出说明：

- `Inherited BaseValue(5)` 可以显式调用父类指定方法
- 上述例子依次输出 `6`、`9`

析构函数 `destroy`：

代码块身份：可直接照写示例

```tsl
h := new Human();
writeLn(class(Human).GetCount());
h := nil;
writeLn(class(Human).GetCount());

type Human = class
public
    static count;
    function create();
    begin
        count := (count ?: 0) + 1;
    end;
    function destroy();
    begin
        count--;
        writeLn(count);
    end;
    class function GetCount();
    begin
        return count;
    end;
end;
```

输出说明：

- 创建对象后，`class(Human).GetCount()` 输出 `1`
- `h := nil` 时会触发 `destroy()`，中途输出 `0`
- 释放后再次读取 `class(Human).GetCount()` 也输出 `0`

`self(0)` / `self(1)`：

代码块身份：可直接照写示例

```tsl
c := new ChildBox();
a := c.MakeOwner();
b := c.MakeBase();
writeLn(a is class(ChildBox));
writeLn(b is class(ChildBox));

type BaseBox = class
public
    function MakeOwner();
    begin
        return self(1);
    end;
    function MakeBase();
    begin
        return self(0);
    end;
end;
type ChildBox = class(BaseBox)
end;
```

输出说明：

- `self(1)` 返回的对象在这个例子里是 `ChildBox`，因此输出 `1`
- `self(0)` 返回的对象在这个例子里不是 `ChildBox`，因此输出 `0`

### 跨 unit 类路径

`unit` 中的嵌套类路径创建属于跨 `unit` 边界，这里用 `text` 展示骨架：

代码块身份：配置片段 / 概念骨架
代码块说明：跨 `unit` 类路径骨架；依赖 `unit` 查找路径和目标环境对象模型，不是可直接复制的单文件最小示例。

```text
// Unit1.tsf
unit Unit1;

interface

type Class1 = class
public
    type Class2 = class
    public
        value;
    end;
end;

implementation

end.

// main.tsl

uses Unit1;
obj := createObject("Unit1.Class1.Class2");
obj.value := 42;
writeLn(obj.value);
```

输出说明：

- 这个骨架只说明字符串路径创建需要按跨 `unit` 边界处理。
- 生成实际代码前先回看 [09_units_and_scope.md](09_units_and_scope.md) 和 [19_namespace_libpath_and_unit_runtime.md](19_namespace_libpath_and_unit_runtime.md)。
- 不要把它直接改写成普通本地类的 `new ClassName()` 模式。

继承单元中的嵌套类路径属于跨 `unit` 边界，这里也用 `text` 展示骨架：

代码块身份：配置片段 / 概念骨架
代码块说明：跨 `unit` 父类路径骨架；依赖 `unit` 查找路径和目标环境对象模型，不是可直接复制的单文件最小示例。

```text
// Unit1.tsf
unit Unit1;

interface

type Class1 = class
public
    type Class2 = class
    public
        value;
    end;
end;

implementation

end.

// main.tsl

uses Unit1;

type MyNewClass = class(Unit1.Class1.Class2)
end;
```

输出说明：

- 这个骨架只说明继承声明里的父类路径形态属于跨 `unit` 边界。
- 不要把它泛化成普通表达式 `class(Unit1.Class1.Class2)`。
- 对象创建方式不要从普通本地类 `new ClassName()` 规则直接外推。

能力探测：

代码块身份：可直接照写示例

```tsl
{$ifdef parentClassInUnit}
writeLn(1);
{$else}
writeLn(0);
{$endif}
```

输出说明：

- `{$ifdef parentClassInUnit}` 输出 `1`

## 默认生成模板

最短类骨架直接复用本页开头的“最短类骨架”。

代码块身份：可直接照写示例

```tsl
type Person = class
end;
```

## 禁止项

- 以下误写不可照写；本节只收容易被智能体从相近语言或相邻 TSL 写法外推出来的边界，不重复列已经在核心规则里明确的普通规则。
- 不把裸类名成员访问当成文档事实；类方法和静态字段默认用 `class(Name).Member` 或文档明确反射入口。
- 把 `self()` 当成本页明确的工厂式写法。
- 把 `private` / `protected create` 当成一定会自动执行的构造函数。
- 在子类里把父类 `private` 成员当成 `protected` 成员来访问。
- 把带命名空间的 `new a.b.c` 直接当成文档事实。
- 把 `class(Unit1.Class1.Class2)` 这种表达式写法直接当成文档事实。

代码块身份：反例 / 不可照写

```text
obj := new Unit1.Class1.Class2();
```

上面这种写法不作为可写事实；本页只把 `createObject("Unit1.Class1.Class2")` 这种路径创建写成可用事实。

代码块身份：反例 / 不可照写

```text
cls := class(Unit1.Class1.Class2);
```

上面这种表达式写法不作为可写事实；本页只把继承声明里的 `class(Unit1.Class1.Class2)` 和字符串路径创建写成可用事实。

代码块身份：反例 / 不可照写

```text
MathBox.Add(1, 2);
Human.count := 7;
```

上面这种裸类名成员访问不作为可写事实；类方法可用 `class(MathBox).Add(...)` 或 `findClass("MathBox").Add(...)` 调用，静态字段可用 `class(Human).count` 访问。

代码块身份：反例 / 不可照写

```text
self();
```

上面这种无参工厂式 `self()` 不作为可写事实；本页只把 `self(0)` / `self(1)` 这种工厂式写成可用事实。普通成员访问仍直接写成员名，不加 `self` 前缀。
