# TSL 对象运行时与反射

这一篇收拢对象模型里不适合继续堆在基础类主线里的运行时内容：类信息、函数句柄、对象状态、引用计数、运行时对象枚举、弱引用与自动弱引用。普通类声明、继承、构造和普通对象创建仍以 [08_objects_and_classes.md](08_objects_and_classes.md) 为主线；本页只在用户明确需要运行时反射、内省、对象生命周期或弱引用能力时进入。

## 本篇职责

<!-- section-id: syntax-19-001 -->

回答“怎样检查类信息、函数信息、对象运行时状态、弱引用创建和弱引用访问判定”。本页拥有反射与运行时状态查询；普通类编写、对象创建和方法调用不在本页收口，也不要因为本页存在反射能力就把普通调用改写成动态查找。

## 核心规则

<!-- section-id: syntax-19-002 -->

- 对象值可以用 `ifObj(...)` 做显式判定。
- 普通本地类创建仍默认使用 `new ClassName()`；本页的 `createObject(cls)` 只用于运行时已经拿到类类型变量后的创建场景。
- `class(Name)` 和 `findClass("Name")` 都可以拿到类类型。
- `obj is classType` 可直接拿类类型变量来做实例判定。
- `findClass("ParentName", obj)` 可以把实例对象降成指定父类视图。
- `obj.classInfo(1)` 会返回类类型，并且这个类类型可以继续交给 `createObject(...)`。
- `obj.classInfo()` 至少可以直接读出 `["classname"]`。
- `objectstate(self)` 在构造函数内部返回 `1`；构造结束后对实例再取 `objectstate(obj)` 返回 `2`。
- `tslassigning` 在属性写入函数里输出 `1`，在属性读取函数里输出 `0`。
- `findFunction("MethodName", obj)` 可以拿到实例方法句柄。
- `findFunction("MethodName", class(A))` 可以拿到类方法句柄。
- `thisFunction(obj.Method)` 和 `thisFunction(class(A).Method)` 也可用。
- 句柄调用方式默认写 `f.do(...)`。
- `findOverLoad(argc, "Name", obj)` 可以按参数个数取到对应重载方法。
- `functionInfo` 至少可以读出 `functionname`、`returntype`、`classname` 这几个字段。
- `tslObjects(1)` 会按类名分组返回对象信息，并且分组项里的 `"obj"` 字段可以重新拿到可调用对象。
- 对象赋值会延长对象存活；只有最后一个引用清空后才会触发 `destroy()`。
- `self` 表示本实例对象引用；本页在需要该引用的场景使用它，如传给运行时函数（`objectstate(self)`）或保存自身引用（`sca := self;`）。普通成员访问仍按 [08_objects_and_classes.md](08_objects_and_classes.md) 的规则，不加 `self` 前缀。
- 弱引用能力的条件编译宏是 `weakptr`；自动弱引用相关宏是 `autoWeak`。
- `weakRef(obj)` 可以创建弱引用；对象仍存活时，`weakref_get(w)` 可以拿回强引用。
- `checkWeakRef(w)` 在对象仍存活时返回 `1`，对象已释放后返回 `-1`。
- 对已经失效的弱引用直接调用 `weakref_get(w)` 会运行报错，不要把它当成“安全返回 nil”的接口。
- 类成员上的 `[weakRef] field;` 和 `[autoRef] field;` 可以通过。
- `[weakRef]` 成员不会阻止对象析构；强引用释放后，对象会正常销毁。
- 类内段落式 `weakRef` / `autoRef`（不带分号）像 `public` / `private` 一样切换后续成员的弱引用属性，可写；只有带分号的 `weakRef;` / `autoRef;` 才报 `invalid class definition`。

## 可直接照写示例

<!-- section-id: syntax-19-003 -->

### 对象值与类类型

<!-- section-id: syntax-19-004 -->

<!-- tags: 判断是不是对象, 取类类型, 类型识别 -->

对象值的最小显式判定：

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

```tsl
obj := new RuntimeBox();
writeLn(ifObj(obj));

type RuntimeBox = class
end;
```

结果说明：

- 输出 `1`
- 说明 `new ...()` 得到的普通对象值可以用 `ifObj(...)` 判定

代码块身份：输出片段

```text
1
```

`findClass(...)` 与类类型变量：

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

```tsl
classA := findClass("A");
obj := new A();
writeLn(classA.FuncA());
writeLn(obj is classA);

type A = class
public
    class function FuncA();
    begin
        return "ClassA";
    end;
end;
```

结果说明：

- 依次输出 `ClassA`、`1`
- 说明 `findClass("A")` 可以拿到类类型
- 说明类类型变量可以直接调用类方法
- 也说明 `obj is classA` 这种“实例对类类型变量做判定”的写法可用

`findClass("Parent", obj)` 的父类视图：

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

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

type ClassA = class
public
    function Func(); virtual;
    begin
        return "ClassA";
    end;
end;
type ClassB = class(ClassA)
public
    function Func(); override;
    begin
        return "ClassB";
    end;
end;
```

结果说明：

- 输出 `ClassA`
- 说明 `findClass("ClassA", objb)` 可以把子类实例降成父类视图
- 也说明之后的方法分派会按父类视图执行

### 类信息与对象状态

<!-- section-id: syntax-19-005 -->

<!-- tags: 对象属于哪个类, 反射, 类名, 运行时看类信息 -->

`classInfo(1)` 返回类类型：

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

```tsl
obj := new A();
cls := obj.classInfo(1);
obj2 := createObject(cls);
writeLn(obj2 is class(A));

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

结果说明：

- 输出 `1`
- 说明 `obj.classInfo(1)` 可以返回类类型
- 也说明这个类类型可以继续交给 `createObject(...)`
- 这个 `createObject(cls)` 只适用于“类类型来自运行时”的场景；普通本地类创建以 [08_objects_and_classes.md](08_objects_and_classes.md) 的 `new ClassName()` 规则为准

`classInfo()` 返回的映射字段：

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

```tsl
obj := new A();
info := obj.classInfo();
writeLn(info["classname"]);

type A = class
end;
```

结果说明：

- 输出 `a`
- 说明 `classInfo()` 至少可以直接读出 `["classname"]`
- 这个最小例子里，类名表现为小写标识符

`objectstate(...)`：

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

```tsl
oa := new Ca("abc");
writeLn(objectstate(oa));

type Ca = class
public
    static sca;
    function create(n);
    begin
        sca := self;
        writeLn(objectstate(self));
    end;
end;
```

结果说明：

- 依次输出 `1`、`2`
- 说明构造函数内部对象状态是 `1`
- 构造结束后，再对实例取 `objectstate(...)` 会得到 `2`

`tslassigning`：

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

```tsl
o := new Ca();
o.c := 3;
writeLn(o.c);

type Ca = class
public
    value;
    property c read Getc write Setc;
    function Setc(v);
    begin
        writeLn(tslassigning);
        value := v;
    end;
    function Getc();
    begin
        writeLn(tslassigning);
        return value;
    end;
end;
```

结果说明：

- 依次输出 `1`、`0`、`3`
- 说明属性写入函数里 `tslassigning` 为 `1`
- 属性读取函数里 `tslassigning` 为 `0`

### 函数句柄与重载

<!-- section-id: syntax-19-006 -->

<!-- tags: 按名字找函数, 动态调用方法, 拿方法引用 -->

`findFunction(...)` 查找实例方法和类方法：

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

```tsl
obj := new A();
f1 := findFunction("Add", obj);
f2 := findFunction("AddClass", class(A));
writeLn(f1.do(3, 4));
writeLn(f2.do(5, 6));

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

结果说明：

- 依次输出 `7`、`11`
- 说明 `findFunction(..., obj)` 能拿到实例方法句柄
- 说明 `findFunction(..., class(A))` 能拿到类方法句柄
- 也说明句柄调用方式默认写 `f.do(...)`

`thisFunction(...)` 也可以直接拿方法句柄：

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

```tsl
obj := new A();
f1 := thisFunction(obj.Add);
f2 := thisFunction(class(A).AddClass);
writeLn(f1.do(3, 4));
writeLn(f2.do(5, 6));

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

结果说明：

- 依次输出 `7`、`11`
- 说明 `thisFunction(...)` 也可以稳定得到实例方法和类方法句柄

`findOverLoad(...)`：

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

```tsl
t := findOverLoad(2, "Fun", new TestClass());
writeLn(t.do(1, 2));

type TestClass = class
public
    function Fun(p1, p2); overload;
    begin
        return p1 + p2;
    end;
    function Fun(p1); overload;
    begin
        return p1 + 10;
    end;
end;
```

结果说明：

- 输出 `3`
- 说明 `findOverLoad(2, "Fun", obj)` 可以按参数个数拿到对应重载方法

### 函数信息、对象枚举与生命周期

<!-- section-id: syntax-19-007 -->

<!-- tags: 列出所有对象, 引用计数, 对象什么时候释放, 函数元信息 -->

`functionInfo` 的文档字段：

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

```tsl
f := thisFunction(class(A).Add);
info := f.functionInfo;
writeLn(info["functionname"]);
writeLn(info["returntype"]);
writeLn(info["classname"]);

type A = class
public
    class function Add(a: lhs; b: rhs): sum_t;
    begin
        return a + b;
    end;
end;
```

结果说明：

- 依次输出 `add`、`sum_t`、`a`
- 说明 `functionInfo["functionname"]`、`["returntype"]`、`["classname"]` 都可直接读取
- 这个最小例子里，返回的函数名和类名表现为小写标识符

`tslObjects(1)`：

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

```tsl
objA := new TestClass01(100);
objB := new TestClass01(101);

objsInfo := tslObjects(1);
writeLn(length(objsInfo["TestClass01"]));
newObjA := objsInfo["TestClass01"][0, "obj"];
writeLn(newObjA is class(TestClass01));
writeLn(newObjA.add(1, 2));

type TestClass01 = class
public
    value;
    function create(_value);
    begin
        value := _value;
    end;
    class function Add(x, y);
    begin
        return x + y;
    end;
end;
```

结果说明：

- 依次输出 `2`、`1`、`3`
- 说明 `tslObjects(1)` 可以按类名拿到对象分组
- 分组项里的 `"obj"` 字段可以重新拿到可调用对象

引用计数的最小行为：

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

```tsl
o1 := new T1();
o2 := o1;
o1 := 0;
writeLn("O1 to Zero");
o2 := 0;
writeLn("O2 to Zero");

type T1 = class
public
    function destroy();
    begin
        writeLn("destroy");
    end;
end;
```

结果说明：

- 依次输出 `O1 to Zero`、`destroy`、`O2 to Zero`
- 说明只清空第一个别名时对象还不会释放
- 最后一个引用清空后才会触发 `destroy()`

### 弱引用与自动弱引用

<!-- section-id: syntax-19-008 -->

<!-- tags: 循环引用, 弱引用, 自动释放, 内存泄漏 -->

弱引用能力的条件编译判定：

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

```tsl
{$ifdef weakptr}
writeLn('weakptr');
{$else}
writeLn('Noweakptr');
{$endif}
{$ifdef autoWeak}
writeLn('autoWeak');
{$else}
writeLn('NoAutoWeak');
{$endif}
```

结果说明：

- 会输出 `weakptr`
- 也会输出 `autoWeak`
- 说明弱引用能力的条件编译开关应写成 `weakptr`，自动弱引用能力的开关应写成 `autoWeak`

对象仍然存活时：

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

```tsl
a := new TNode('A');
w := weakRef(a);
writeLn(checkWeakRef(w));
s := weakref_get(w);
writeLn(s.name);

type TNode = class
public
    name;
    function create(v);
    begin
        name := v;
    end;
end;
```

结果说明：

- `checkWeakRef(w)` 返回 `1`
- `weakref_get(w)` 能拿回强引用
- 通过拿回的强引用可继续访问对象成员，此处输出 `A`

同一 `TNode` 类型下，强引用释放后的主体模板：

代码块身份：配置片段 / 概念骨架

```tsl
a := new TNode('A');
w := weakRef(a);
writeLn(checkWeakRef(w));
a := nil;
writeLn(checkWeakRef(w));
```

结果说明：

- 创建后 `checkWeakRef(w)` 返回 `1`
- 把最后一个强引用设为 `nil` 后，`checkWeakRef(w)` 返回 `-1`

失效弱引用不应直接取强引用：

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

```text
a := new TNode('A');
w := weakRef(a);
a := nil;
t := weakref_get(w);
```

上面这种写法不作为可写事实，会运行报错；访问弱引用前应先做 `checkWeakRef(w)` 判定。

成员级标记可以直接写在类里：

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

```tsl
writeLn(1);

type TChild = class
public
    [weakRef] owner1;
    [autoRef] owner2;
end;
```

结果说明：

- 上述写法可以正常执行，并输出 `1`
- 说明 `[weakRef] field;` 和 `[autoRef] field;` 都能通过

`[weakRef]` 成员不会阻止对象析构：

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

```tsl
a := new TA();
b := new TB();
b.Bind(a);
a := nil;
writeLn('AfterNil');

type TA = class
public
    function destroy();
    begin
        writeLn('Destroy');
    end;
end;

type TB = class
public
    [weakRef] owner;
    function Bind(v);
    begin
        owner := v;
    end;
end;
```

结果说明：

- 输出顺序是 `Destroy`、`AfterNil`
- 说明 `TB.owner` 作为 `[weakRef]` 成员，不会把 `TA` 实例继续强持有

## 禁止项

<!-- section-id: syntax-19-009 -->

- 不要在运行时/反射页发明类声明、继承或构造语法；类基础事实见 [08_objects_and_classes.md](08_objects_and_classes.md)。
- 不要为了普通对象创建先写 `findClass(...)` / `createObject(...)`；普通本地类创建以 [08_objects_and_classes.md](08_objects_and_classes.md) 为准，默认用 `new ClassName()`。
- 不要把函数句柄直接当普通函数直调；默认调用方式是 `f.do(...)`。
- 不要把 `functionInfo` 示例外推成所有字段都可读。
- 不要把引用计数样例泛化成所有对象销毁时机；只按本页文档行为描述。
- 不要把 `objectstate(self)` 里的 `self` 泛化成普通成员访问都要加 `self` 前缀。
- 不要把弱引用能力的条件编译宏写成 `weakRef`。
- 不要以为 `weakref_get(deadWeakRef)` 会像普通可空访问那样安全返回 `nil`。
- 不要给段落切换关键字加分号；`weakRef;` / `autoRef;`（带分号）会报 `invalid class definition`，段落式要写成不带分号的 `weakRef` / `autoRef`。
- 不要以为 `[weakRef]` 成员会继续强持有对象。

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

```text
type TChild = class
public
    owner0;
    [weakRef] owner1;
    weakRef;
    owner2;
    autoRef;
    owner3;
end;
```

上面这种给段落切换关键字**加分号**（`weakRef;` / `autoRef;`）的写法会编译失败，错误信息包含 `invalid class definition`。关键在分号：段落切换关键字要像 `public` / `private` 那样**不带分号**独占一行，带分号后被当成独立语句才报错。正确的段落式写法见下一段。

### 段落式 `weakRef` / `autoRef` 的正确写法

<!-- section-id: syntax-19-010 -->

<!-- tags: 弱引用段落写法, weakRef 不带分号 -->

段落式 `weakRef` / `autoRef`（不带分号）像 `public` / `private` 一样切换后续成员的弱引用属性：

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

```tsl
o := new AutoWeakTest();
o.fa := 1;
o.fb := 2;
writeLn(o.fa, ',', o.fb);

type AutoWeakTest = class
public
    fa;
weakRef
    fOnClick;
    fOnDblClick;
autoRef
    fb;
end;
```

结果说明：

- 输出 `1,2`
- `weakRef`（不带分号）打开后续成员的自动弱引用开关，`fOnClick` / `fOnDblClick` 成为弱引用成员
- `autoRef`（不带分号）关闭开关，`fb` 恢复为强引用成员
- 段落切换只影响其后、下一个切换关键字之前的成员；`fa` 在初始（强引用）段
- 这与行内 `[weakRef] field;` 是两种可用写法：行内只作用于单个成员，段落式作用于整段
