Files
playbook/skills/tsl-syntax-reference/references/19_object_runtime_and_introspection.md
T
csh 40a9885eb4 feat(tsl-syntax-reference): harden retrieval and restructure pages
- flag weak candidates (no intent/heading/identifier/tag hit) and exit 2
  when every candidate is weak: mis-hits used to be indistinguishable
  from real hits, so the retry-with-better-terms loop never fired
- accept multiple ids per --section for batch fetch, failing atomically
  on any unknown id so a partial fetch cannot pass as complete
- move query synonyms and page intent aliases to data/lexicon.json and
  enforce alias/page correspondence in --check; curation data no longer
  lives in the engine
- document the weak-hit rule, batch fetch and prelude-once guidance in
  SKILL.md, with curation discipline in data/README.md
- drop 11_pitfalls.md, renumber the trailing pages and spread retrieval
  tags across topics; lexicon keys are page filenames, so the renumbering
  and the new --check rule cannot land in separate commits
2026-07-29 15:45:47 +08:00

15 KiB
Raw Blame History

TSL 对象运行时与反射

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

本篇职责

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

核心规则

  • 对象值可以用 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 至少可以读出 functionnamereturntypeclassname 这几个字段。
  • tslObjects(1) 会按类名分组返回对象信息,并且分组项里的 "obj" 字段可以重新拿到可调用对象。
  • 对象赋值会延长对象存活;只有最后一个引用清空后才会触发 destroy()
  • self 表示本实例对象引用;本页在需要该引用的场景使用它,如传给运行时函数(objectstate(self))或保存自身引用(sca := self;)。普通成员访问仍按 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

可直接照写示例

对象值与类类型

对象值的最小显式判定:

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

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

type RuntimeBox = class
end;

结果说明:

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

代码块身份:输出片段

1

findClass(...) 与类类型变量:

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

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;

结果说明:

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

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

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

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) 可以把子类实例降成父类视图
  • 也说明之后的方法分派会按父类视图执行

类信息与对象状态

classInfo(1) 返回类类型:

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

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.mdnew ClassName() 规则为准

classInfo() 返回的映射字段:

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

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

type A = class
end;

结果说明:

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

objectstate(...)

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

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

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

结果说明:

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

tslassigning

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

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;

结果说明:

  • 依次输出 103
  • 说明属性写入函数里 tslassigning1
  • 属性读取函数里 tslassigning0

函数句柄与重载

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

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

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;

结果说明:

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

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

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

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;

结果说明:

  • 依次输出 711
  • 说明 thisFunction(...) 也可以稳定得到实例方法和类方法句柄

findOverLoad(...)

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

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) 可以按参数个数拿到对应重载方法

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

functionInfo 的文档字段:

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

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;

结果说明:

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

tslObjects(1)

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

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;

结果说明:

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

引用计数的最小行为:

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

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 ZerodestroyO2 to Zero
  • 说明只清空第一个别名时对象还不会释放
  • 最后一个引用清空后才会触发 destroy()

弱引用与自动弱引用

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

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

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

结果说明:

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

对象仍然存活时:

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

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 类型下,强引用释放后的主体模板:

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

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

结果说明:

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

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

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

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

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

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

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

writeLn(1);

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

结果说明:

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

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

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

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;

结果说明:

  • 输出顺序是 DestroyAfterNil
  • 说明 TB.owner 作为 [weakRef] 成员,不会把 TA 实例继续强持有

禁止项

  • 不要在运行时/反射页发明类声明、继承或构造语法;类基础事实见 08_objects_and_classes.md
  • 不要为了普通对象创建先写 findClass(...) / createObject(...);普通本地类创建以 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] 成员会继续强持有对象。

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

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

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

段落式 weakRef / autoRef 的正确写法

段落式 weakRef / autoRef(不带分号)像 public / private 一样切换后续成员的弱引用属性:

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

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; 是两种可用写法:行内只作用于单个成员,段落式作用于整段