playbook/docs/tsl/syntax/09_objects_and_classes.md

29 KiB
Raw Blame History

Objects And Classes

文档类型:语法主线 是否可直接用于生成代码:仅部分 是否含已验证可执行示例:是 是否含已验证反例:是 遇到不确定时跳转到:10_units_and_scope.md(优先)、23_object_runtime_and_introspection.md12_pitfalls.md

手册位置:第 9 篇,共 32 篇。上一篇:08_control_flow.md。下一篇:10_units_and_scope.md

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

这一篇解决什么问题

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

本页后半段有少量依赖 unit 的双文件示例;如果你还没建立 unit / uses 的多文件心智模型,先看 10_units_and_scope.md

必须记住的规则

  • 类定义统一按 type Name = class ... end; 写。
  • 顶层类声明当前已验证可以放在松散语句之后(单向允许)。
  • 顶层类声明当前也已验证可以放在顶层 function / procedure 之后。
  • 类声明当前没有通过写在函数体内部。
  • 在松散语句脚本里,可以先写松散语句再进入顶层类声明;但一旦进入顶层 type Name = class ... end; 声明,后面不能再回到松散语句模式。
  • 类里可以直接写字段和方法。
  • 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()
  • 已验证的对象创建写法至少有三种:CreateObject("ClassName")CreateObject(ClassType)new ClassName()
  • 如果没有特殊需求,默认优先用 new ClassName();只有需要字符串路径、类类型变量或跨 unit 路径时,再改用 CreateObject(...)
  • 如果类里定义了 function create(...)newCreateObject("ClassName", ...)CreateObject(ClassType, ...) 都已验证可以透传构造参数,也都已验证支持默认参数和命名参数。
  • 当前已验证的析构写法是无参 function destroy();;把对象引用设为 nil 时会触发它。
  • 工厂式 self(0) / self(1) 已验证可用;当前解释器没有通过 self() 这种无参工厂式写法。
  • 已做双文件运行验证:如果类定义在 unit 的嵌套路径里,可以用 CreateObject("Unit1.Class1.Class2") 这种字符串路径创建对象。
  • 已做双文件运行验证:可以写 type MyNewClass = class(Unit1.Class1.Class2) 直接继承单元里的嵌套类路径。
  • 当前环境里 {$IFDEF ParentClassInUnit} 为真,可用于探测“继承和构造单元中的类”能力是否可用。
  • private / protected / public 已验证可用;类开头未写可见性时,成员默认按 public 处理。
  • 同一个可见性段里后续没有切换关键字的成员,会沿用前一个可见性。
  • 外部访问 private / protected 字段或方法,会在执行时报对象成员访问错误。
  • 子类可以通过 self 访问父类 protected 成员;不能通过 self 访问父类 private 成员。
  • create 写成 privateprotected 时,对象仍能创建,但 newCreateObject(...) 不会执行该构造逻辑;构造函数应保持 public
  • 更复杂的多重继承边界和更复杂的对象模型还没有在这篇里完整展开。

已验证语法

最短类骨架:

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

代码块身份:已验证可执行示例

type Person = class
end;

字段、方法和 CreateObject

代码块身份:已验证可执行示例

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

function MakePerson();
begin
    return CreateObject("Person");
end;

类声明位置:

代码块身份:已验证可执行示例

obj := CreateObject("MyClass");
obj.value := 5;
WriteLn(obj.value);

type MyClass = class
    value;
end;

已验证运行结果:

  • 输出 5
  • 说明在当前解释器里,顶层类声明可以放在松散语句之后
  • 同时也说明 CreateObject("MyClass") 可以解析到后面才出现的类声明

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

代码块身份:已验证可执行示例

function MakeObj();
begin
    return 1;
end;

type MyClass = class
    value;
end;

已验证结果:

  • 上述 .tsf 例子可以编译通过

类声明位置的反例:

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

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

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

已验证结果:

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

static 字段:

代码块身份:已验证可执行示例

program test;
type THuman = class
    static
    mCount;
    function create();
    begin
        mCount := (mCount ?: 0) + 1;
    end;
end;
begin
    class(THuman).mCount := 100;
    WriteLn(class(THuman).mCount);
    h := new THuman();
    WriteLn(class(THuman).mCount);
    WriteLn(h.mCount);
end.

已验证运行结果:

  • class(THuman).mCount := 100 可以直接写静态字段
  • class(THuman).mCount 先输出 100
  • 创建对象后,class(THuman).mCount 输出 101
  • 通过实例读取 h.mCount 也输出 101

同一字段也接受内联声明:

代码块身份:已验证可执行示例

program test;
type THuman = class
    static mCount;
    function create();
    begin
        mCount := (mCount ?: 0) + 1;
    end;
end;
begin
    class(THuman).mCount := 100;
    h := new THuman();
    WriteLn(class(THuman).mCount);
end.

已验证运行结果:

  • static mCount; 这种内联写法也可以通过
  • 上述例子中的 class(THuman).mCount 输出 101

const 成员:

代码块身份:已验证可执行示例

program test;
type C = class
public
    const mA = 1;
    static const mB = mA + 10;
    function TestConst();
    begin
        return mA + mB;
    end;
    function TestConstInParam(b = mB);
    begin
        return b;
    end;
end;
begin
    o := new C();
    WriteLn(o.TestConst());
    WriteLn(o.TestConstInParam());
    WriteLn(o.mA);
    WriteLn(class(C).mB);
end.

已验证运行结果:

  • 依次输出 1211111
  • 说明类里可以定义实例常量和 static const
  • 说明实例常量可通过实例读取,static const 可通过 class(C) 读取
  • 说明成员常量当前也可用于成员函数默认参数

self 指向当前实例:

代码块身份:已验证可执行示例

program test;
type Counter = class
    value;
    function create(v);
    begin
        self.value := v;
    end;
    function Inc();
    begin
        self.value := self.value + 1;
        return self.value;
    end;
end;
begin
    c := new Counter(10);
    WriteLn(c.Inc());
end.

已验证运行结果:

  • c.Inc() 输出 11

可见性 private / protected / public

代码块身份:已验证可执行示例

program test;
type A = class
    value;
private
    secret;
    backup;
public
    function create();
    begin
        self.secret := 2;
        self.backup := 3;
    end;
    function ReadSecret();
    begin
        return self.secret + self.backup;
    end;
end;
begin
    a := new A();
    a.value := 9;
    WriteLn(a.value);
    WriteLn(a.ReadSecret());
end.

已验证运行结果:

  • 类开头没有显式可见性时,value 默认按 public 处理,因此 a.value := 9 可以直接写。
  • backup 跟在 private 段后面,没有重新切换关键字,因此仍按 private 处理。
  • 上述例子依次输出 95

protected 可供子类通过 self 访问:

代码块身份:已验证可执行示例

program test;
type A = class
protected
    value;
    function Hidden();
    begin
        return 88;
    end;
end;
type B = class(A)
public
    function SetValue(v);
    begin
        self.value := v;
    end;
    function GetValue();
    begin
        return self.value;
    end;
    function TryCall();
    begin
        return self.Hidden();
    end;
end;
begin
    b := new B();
    b.SetValue(66);
    WriteLn(b.GetValue());
    WriteLn(b.TryCall());
end.

已验证运行结果:

  • 子类里可以通过 self.value 访问父类 protected 字段。
  • 子类里也可以通过 self.Hidden() 调用父类 protected 方法。
  • 上述例子依次输出 6688

private 的失败场景,因为是已验证反例,这里用 text 展示:

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

// 外部访问 private 字段
program test;
type A = class
private
    secret;
public
    function create();
    begin
        self.secret := 7;
    end;
end;
begin
    a := new A();
    WriteLn(a.secret);
end.

// 子类访问父类 private 字段
program test;
type A = class
private
    secret;
public
    function ReadSecret();
    begin
        return self.secret;
    end;
end;
type B = class(A)
public
    function SetSecret(v);
    begin
        self.secret := v;
    end;
end;
begin
    b := new B();
    b.SetSecret(77);
    WriteLn(b.ReadSecret());
end.

已验证运行结果:

  • 外部访问 a.secret 会在执行时报对象成员访问错误。
  • 子类里通过 self.secret 访问父类 private 字段也会在执行时报错。
  • 同样的规则也已单独用 private / protected 方法访问做过复核:外部不能调 private / protected 方法,子类只能调 protected 方法,不能调 private 方法。

create 建议保持 public

代码块身份:已验证可执行示例

program test;
type A = class
public
    value;
private
    function create(v);
    begin
        self.value := v;
    end;
end;
begin
    a := new A(111);
    WriteLn(a.value);
end.

已验证运行结果:

  • a := new A(111) 可以创建对象。
  • 上述例子里的 a.value 输出 <NIL>,说明 private create 没有执行。
  • 同样的结果已单独用 protected createCreateObject("A", ...)CreateObject(class(A), ...) 复核;当前解释器里构造函数应保持 public

基础 property

代码块身份:已验证可执行示例

program test;
type MyBox = class
    _value;
    function SetValue(v);
    begin
        if v > 0 then
            _value := v;
    end;
    property Value read _value write SetValue;
end;
begin
    b := new MyBox();
    b.Value := 7;
    WriteLn(b.Value);
    b.Value := -1;
    WriteLn(b.Value);
end.

已验证运行结果:

  • b.Value := 7 后,b.Value 输出 7
  • b.Value := -1 后,b.Value 仍输出 7

带类型注解的 property

代码块身份:已验证可执行示例

program test;
type MyBox = class
    _value;
    function SetValue(v);
    begin
        _value := v;
    end;
    property Value: score_value read _value write SetValue;
end;
begin
    b := new MyBox();
    b.Value := 9;
    WriteLn(b.Value);
end.

已验证运行结果:

  • property Value: score_value ... 这种类型注解写法可以通过
  • 上述例子中的 b.Value 输出 9

索引型 property

代码块身份:已验证可执行示例

program test;
type A = class
    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;
begin
    aa := new A();
    aa.idx(0) := "abc";
    WriteLn(aa.idx(0));
end.

已验证运行结果:

  • aa.idx(0) := "abc" 可以写入索引 property
  • aa.idx(0) 输出 abc

固定 index property

代码块身份:已验证可执行示例

program test;
type A = class
    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;
begin
    aa := new A();
    aa.idx0 := "abc";
    WriteLn(aa.idx0);
    WriteLn(aa.idx(0));
end.

已验证运行结果:

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

固定字符串索引:

代码块身份:已验证可执行示例

program test;
type A = class
    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;
begin
    aa := new A();
    aa.school := "math";
    WriteLn(aa.school);
    WriteLn(aa.idx("High school"));
end.

已验证运行结果:

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

参数化 property

代码块身份:已验证可执行示例

program test;
type MyDate = class
    _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;
begin
    d := new MyDate();
    d.DateV(2025, 8) := 10;
    WriteLn(d.DateV());
end.

已验证运行结果:

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

参数化 property 的 accessor 方法参数:

代码块身份:已验证可执行示例

program test;
type A = class
    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): slot_type read getItem write setItem;
end;
begin
    aa := new A();
    aa.Item(2) := "x";
    WriteLn(aa.Item(2));
end.

已验证运行结果:

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

new 关键字:

代码块身份:已验证可执行示例

type Person = class
end;

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

通过类类型创建对象:

代码块身份:已验证可执行示例

program test;
type Person = class
    value;
end;
begin
    cls := FindClass("Person");
    obj := CreateObject(cls);
    obj.value := 42;
    WriteLn(obj.value);
end.

已验证运行结果:

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

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

代码块身份:已验证可执行示例

program test;
type Person = class
    value;
    function create(v);
    begin
        value := v;
    end;
end;
begin
    cls := class(Person);
    obj := CreateObject(cls, 44);
    WriteLn(obj.value);
end.

已验证运行结果:

  • class(Person) 可以拿到 Person 的类类型
  • CreateObject(cls, 44) 输出 44

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

代码块身份:已验证可执行示例

program test;
type Person = class
    a;
    b;
    c;
    function create(a, b = 20, c = 30);
    begin
        self.a := a;
        self.b := b;
        self.c := c;
    end;
end;
begin
    a := CreateObject("Person", 11);
    WriteLn(a.a);
    WriteLn(a.b);
    WriteLn(a.c);
    b := new Person(22, c:99);
    WriteLn(b.a);
    WriteLn(b.b);
    WriteLn(b.c);
end.

已验证运行结果:

  • CreateObject("Person", 11) 依次输出 112030
  • new Person(22, c:99) 依次输出 222099
  • 说明构造函数默认参数和命名参数不只适用于普通函数调用,也适用于对象创建

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

代码块身份:已验证可执行示例

program test;
type Person = class
    a;
    b;
    c;
    function create(a, b = 20, c = 30);
    begin
        self.a := a;
        self.b := b;
        self.c := c;
    end;
end;
begin
    cls := FindClass("Person");
    obj := CreateObject(cls, 33, c:77);
    WriteLn(obj.a);
    WriteLn(obj.b);
    WriteLn(obj.c);
end.

已验证运行结果:

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

class function

代码块身份:已验证可执行示例

program test;
type MathBox = class
    class function Add(x, y);
    begin
        return x + y;
    end;
end;
begin
    cls1 := class(MathBox);
    WriteLn(cls1.Add(3, 4));
    cls2 := FindClass("MathBox");
    WriteLn(cls2.Add(5, 6));
end.

已验证运行结果:

  • class(MathBox).Add(...) 可以调用类方法
  • FindClass("MathBox").Add(...) 可以调用类方法
  • 上述例子依次输出 711

overload 方法:

代码块身份:已验证可执行示例

program test;
type TestClass = class
    function fun(p1, p2); overload;
    begin
        return p1 + p2;
    end;
    function fun(p1); overload;
    begin
        return p1 + 10;
    end;
end;
begin
    t := new TestClass();
    WriteLn(t.fun(1, 2));
    WriteLn(t.fun(5));
end.

已验证运行结果:

  • 依次输出 315
  • 说明同名不同参数个数的方法当前可以共存

基础继承:

代码块身份:已验证可执行示例

program test;
type Animal = class
    function Speak();
    begin
        return 1;
    end;
end;
type Dog = class(Animal)
end;
begin
    d := new Dog();
    WriteLn(d.Speak());
end.

已验证运行结果:

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

多重继承:

代码块身份:已验证可执行示例

program test;
type A = class
    function Fa();
    begin
        return 1;
    end;
end;
type B = class
    function Fb();
    begin
        return 2;
    end;
end;
type C = class(A, B)
end;
begin
    obj := new C();
    WriteLn(obj.Fa());
    WriteLn(obj.Fb());
end.

已验证运行结果:

  • type C = class(A, B) 可以同时继承 AB 的方法
  • 上述例子依次输出 12

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

代码块身份:已验证可执行示例

program test;
type A = class
    function Speak();
    begin
        return 1;
    end;
end;
type B = class
    function Speak();
    begin
        return 2;
    end;
end;
type C = class(A, B)
end;
begin
    obj := new C();
    WriteLn(obj.Speak());
end.

已验证运行结果:

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

基础 virtual / override

代码块身份:已验证可执行示例

program test;
type Animal = class
    function Speak(); virtual;
    begin
        return 1;
    end;
end;
type Dog = class(Animal)
    function Speak(); override;
    begin
        return 2;
    end;
end;
begin
    d := new Dog();
    WriteLn(d.Speak());
end.

已验证运行结果:

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

class(BaseClass, ObjectName).MethodName()

代码块身份:已验证可执行示例

program test;
type A = class
    function Speak(); virtual;
    begin
        return 1;
    end;
end;
type B = class(A)
    function Speak(); override;
    begin
        return 2;
    end;
end;
begin
    obj := new B();
    WriteLn(obj.Speak());
    WriteLn(class(A, obj).Speak());
end.

已验证运行结果:

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

FindClass("ClassName", obj) 强制转型:

代码块身份:已验证可执行示例

program test;
type ClassA = class
    function Fuc(); virtual;
    begin
        return 1;
    end;
end;
type ClassB = class(ClassA)
    function Fuc(); override;
    begin
        return 2;
    end;
end;
begin
    objb := new ClassB();
    obj := FindClass("ClassA", objb);
    WriteLn(obj.Fuc());
    WriteLn(obj is class(ClassA));
    WriteLn(obj is class(ClassB));
end.

已验证运行结果:

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

Inherited;

代码块身份:已验证可执行示例

program test;
type A = class
    function Speak(); virtual;
    begin
        WriteLn(1);
    end;
end;
type B = class(A)
    function Speak(); override;
    begin
        Inherited;
        WriteLn(2);
    end;
end;
begin
    obj := new B();
    obj.Speak();
end.

已验证运行结果:

  • Inherited; 会先执行父类同名同参数方法
  • 上述例子依次输出 12

Inherited MethodName(...)

代码块身份:已验证可执行示例

program test;
type A = class
    function BaseValue(x);
    begin
        WriteLn(x + 1);
    end;
end;
type B = class(A)
    function Run();
    begin
        Inherited BaseValue(5);
        WriteLn(9);
    end;
end;
begin
    obj := new B();
    obj.Run();
end.

已验证运行结果:

  • Inherited BaseValue(5) 可以显式调用父类指定方法
  • 上述例子依次输出 69

析构函数 destroy

代码块身份:已验证可执行示例

program test;
type THuman = class
    static mCount;
    function create();
    begin
        mCount := (mCount ?: 0) + 1;
    end;
    function destroy();
    begin
        mCount--;
        WriteLn(mCount);
    end;
    class function GetCount();
    begin
        return mCount;
    end;
end;
begin
    h := new THuman();
    WriteLn(class(THuman).GetCount());
    h := nil;
    WriteLn(class(THuman).GetCount());
end.

已验证运行结果:

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

self(0) / self(1)

代码块身份:已验证可执行示例

program test;
type BaseBox = class
    function MakeOwner();
    begin
        return self(1);
    end;
    function MakeBase();
    begin
        return self(0);
    end;
end;
type ChildBox = class(BaseBox)
end;
begin
    c := new ChildBox();
    a := c.MakeOwner();
    b := c.MakeBase();
    WriteLn(a is class(ChildBox));
    WriteLn(b is class(ChildBox));
end.

已验证运行结果:

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

unit 中的嵌套类路径创建,因为依赖多文件环境,这里用 text 展示:

代码块身份:配置片段 / 概念骨架 代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。

// Unit1.tsf
unit Unit1;
interface
type Class1 = class
    type Class2 = class
        value;
    end;
end;
implementation
end.

// main.tsl
program test;
begin
    obj := CreateObject("Unit1.Class1.Class2");
    obj.value := 42;
    WriteLn(obj.value);
end.

已验证运行结果:

  • CreateObject("Unit1.Class1.Class2") 可以创建对象
  • 上述例子中的 obj.value 输出 42

继承单元中的嵌套类路径,因为依赖多文件环境,这里也用 text 展示:

代码块身份:配置片段 / 概念骨架 代码块说明:已在多文件环境下验证;这里只用来展示结构和结果,不是可直接复制的单文件最小示例。

// Unit1.tsf
unit Unit1;
interface
type Class1 = class
    type Class2 = class
        value;
    end;
end;
implementation
end.

// main.tsl
program test;
type MyNewClass = class(Unit1.Class1.Class2)
end;
begin
    obj := CreateObject("MyNewClass");
    obj.value := 42;
    WriteLn(obj.value);
end.

已验证运行结果:

  • type MyNewClass = class(Unit1.Class1.Class2) 可以通过并继承该路径类
  • 上述例子中的 obj.value 输出 42

能力探测:

代码块身份:已验证可执行示例

program test;
begin
{$IFDEF ParentClassInUnit}
    WriteLn(1);
{$ELSE}
    WriteLn(0);
{$ENDIF}
end.

已验证运行结果:

  • 当前环境里 {$IFDEF ParentClassInUnit} 输出 1

最小可编译示例

如果你只是想先起一个最短类骨架,直接复用本页开头的“最短类骨架”。

代码块身份:已验证可执行示例

type Person = class
end;

常见误写

  • 以下误写中,有代码块的条目都已经单独验证;没有单独代码块展开的文字条目,属于已知语义陷阱或错误泛化,不要把它们当成可写事实。
  • 把类写成裸 class Person
  • 还没建立类定义就直接写 CreateObject("Person")new Person()
  • MathBox.Add(...) 这种裸类名调用直接当成当前解释器已验证的类方法调用写法。
  • THuman.mCount 这种裸类名静态字段访问直接当成当前解释器已验证事实。
  • self() 当成当前解释器已验证的工厂式写法。
  • private / protected create 当成当前解释器一定会自动执行的构造函数。
  • 在子类里把父类 private 成员当成 protected 成员来访问。
  • 把带命名空间的 new a.b.c 直接当成当前解释器已支持事实。
  • class(Unit1.Class1.Class2) 这种表达式写法直接当成当前解释器已验证事实。
  • 以为类声明既然能放在松散语句后面,就还能在 type 后面继续回到松散语句模式。

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

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

上面这种写法在当前已记录验证里没有通过;当前只把 CreateObject("Unit1.Class1.Class2") 这种路径创建写成已验证事实。

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

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

上面这种表达式写法在当前解释器里没有通过;当前只把继承声明里的 class(Unit1.Class1.Class2) 和字符串路径创建写成已验证事实。

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

MathBox.Add(1, 2);

上面这种裸类名调用在当前解释器里没有通过;当前已验证可用的是先拿到类类型,再用 class(MathBox).Add(...)FindClass("MathBox").Add(...) 调用。

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

THuman.mCount := 7;

上面这种裸类名静态字段访问在当前解释器里没有通过;当前已验证可用的是 class(THuman).mCount

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

self();

上面这种无参工厂式 self() 在当前解释器里没有通过;当前只把 self 实例引用,以及 self(0) / self(1) 写成已验证事实。

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

a := 1;
type MyClass = class
    value;
end;
WriteLn(a);

上面这种写法在当前解释器里没有通过,会报 invalid statement。当前只把“松散语句后面可以接顶层类声明”写成已验证事实,不把它泛化成“type 之后还能继续写松散语句”。

跳转指引