- 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
15 KiB
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至少可以读出functionname、returntype、classname这几个字段。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;
结果说明:
- 依次输出
ClassA、1 - 说明
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.md 的new 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;
结果说明:
- 依次输出
1、2 - 说明构造函数内部对象状态是
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;
结果说明:
- 依次输出
1、0、3 - 说明属性写入函数里
tslassigning为1 - 属性读取函数里
tslassigning为0
函数句柄与重载
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;
结果说明:
- 依次输出
7、11 - 说明
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;
结果说明:
- 依次输出
7、11 - 说明
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;
结果说明:
- 依次输出
add、sum_t、a - 说明
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;
结果说明:
- 依次输出
2、1、3 - 说明
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 Zero、destroy、O2 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)返回1weakref_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;
结果说明:
- 输出顺序是
Destroy、AfterNil - 说明
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;是两种可用写法:行内只作用于单个成员,段落式作用于整段