声明访问非导出(non-exported)或虚(拟)实例变量的方法。
[Attributes] [Modifiers] ACCESS <idName>
[([<idParam> [AS | REF <idType>] [, ...])]
[AS <idType>] [<idConvention>]
[CLASS <idClass>]
[=> <expression>]
CRLF
[<Body>]
[END ACCESS]
Attributes | 一个可选的特性列表,用于描述实体的元信息,例如在 MsTest 类库中包含测试的方法/函数上的 [TestMethod] 属性。请注意,当特性写在关键字上方的行时,特性必须在同一行或以分号结尾。 |
Modifiers | 一个可选的修饰符列表,用于指定实体的可见性或范围,例如:PUBLIC, PROTECTED, HIDDEN, INTERNAL, SEALED, ABSTRACT 或 STATIC. |
<idName> | 定义 ACCESS 方法的实例变量的有效标识符名称。 与其他方法一样,ACCESS 方法也是实体;不过,系统为它们使用了独特的命名方案,以防止与其他实体名称相冲突。 ACCESS 方法名在类中必须是唯一的,但可以与应用程序中的其他实体名称相同。 |
TypeParameters | 这适用于具有泛型类型参数的方法。这类似于 `<T>`,用于具有一个名为 T 的类型参数的方法。通常,参数列表中的一个参数也是类型 T。 |
<idParam> | 参数变量。 以这种方式指定的变量会自动声明为局部变量。 这些变量也称为形参(形式参数),用于接收调用实体时传递的参数。 |
AS | REF | OUT | IN <idType> | 指定参数变量的数据类型(称为强类型)。 AS 表示参数必须通过值传递,REF 表示参数必须通过带 @ 操作符的引用传递。OUT 是一种特殊的 REF 参数,不必在调用前赋值,必须在实体内部赋值。IN 参数作为 READONLY 引用传递。 |
列表中的最后一个参数也可以声明为 PARAMS <idType>[] ,这将告诉编译器函数/方法可以接收零个或多个可选参数。 |
使用 CLIPPER 调用约定的函数或方法将被编译为一个带有单个参数的函数,该参数被声明为 Args PARAMS USUAL[] 。 |
AS <idType> | 指定数据类型。如果省略,编译器依据编译选项来决定数据类型,或者是 USUAL,或者是由编译器自行决定。 |
TypeParameterConstraints | 在这里,您可以为类型参数指定约束,例如 WHERE T IS SomeName 或 WHERE T IS New。 |
<idConvention> | 指定此实体的调用约定。 <idConvention> 必须是以下内容之一: |
o CLIPPER
o STRICT
o PASCAL
o CALLBACK
o THISCALL
大多数 调用约定 仅用于向后兼容。 但是,存在两个例外情况: 1. CLIPPER 声明方法具有未类型化参数。通常只有没有声明参数的方法才需要这样做。否则,编译器在检测到未类型化参数时将假定使用 CLIPPER 调用约定。 2. 外部 DLL 的方法和函数可以使用 STRICT、PASCAL、 CALLBACK 调用约定。 |
CLASS <idClass> | 该方法所属的类。对于在 CLASS ... END CLASS 结构之外声明的实体,必须使用该子句。 |
=> <Expression> | 替代实体多行正文(body)的单一表达式。不能与正文(body)一起编译 |
<Body> | 构成此主体代码的程序语句。 |
<Body> 可以包含一个或多个 RETURN 语句,以将控制权返回给调用例程,并作为函数的返回值。如果未指定 RETURN 语句,当函数定义结束时,控制权将返回给调用例程,并且函数将根据指定的返回值数据类型返回一个默认值(如果返回值没有强类型,则返回 NIL)。 |
不能与表达式主体(Expression Body)结合使用。 |
END ACCESS | 可选的结束子句,用于指示 ACCESS 实体的结束 |
ACCESS 声明了一种特殊方法,称为 ACCESS 方法,每次访问命名的实例变量时都会自动执行。
在 CLASS 声明中可以定义四种类型的实例变量。 除了 EXPORT 之外,所有这些都被称为非导出(non-exported)实例变量,因为它们不能直接从外部(即类之外)访问。
例如,如果要从函数中访问对象的非导出(non-exported)实例变量,就必须使用方法。 事实上,这正是不导出(not exporting)变量的目的:通过方法控制对变量的所有引用,从而实现封装。 然而,引用方法的语法显然不同于引用变量的语法。 这违反了封装性,而且非常麻烦,因为类的用户必须知道类的某个属性是如何实现的,才能知道是使用函数式引用还是变量式引用。
例如,请注意当类使用常规方法导出变量时,在函数 UseClass() 中访问实例变量 x 和 y 的区别:
CLASS Test
EXPORT x := 100
INSTANCE y := 10000
METHOD GetValueY() CLASS Test
RETURN y
FUNCTION UseClass()
LOCAL oTest AS Test
oTest := Test{}
? oTest:x
? oTest:GetValueY() // 使用方法访问 y
如果将普通方法替换为 ACCESS 方法,则访问两个变量的语法是相同的,即使其中一个变量被方法所隔离:
ACCESS y CLASS Test
RETURN y
FUNCTION UseClass()
LOCAL oTest AS Test
oTest := Test{}
? oTest:x
? oTest:y // 使用 ACCESS 方法
非导出(non-exported)变量分为三类,每一类都有自己的属性(详见本指南中的 CLASS 语句):
• INSTANCE
• PROTECT
• HIDDEN
INSTANCE 变量专为 ACCESS 和 ASSIGN 方法而设计,这也是其后期绑定的主要原因。 通过定义与 INSTANCE 变量同名的 ACCESS 方法,可以有效地覆盖变量,使所有非赋值引用(包括外部和内部引用)都调用 ACCESS 方法。
例外情况是,在 ACCESS(或 ASSIGN)方法中,同名的实例变量会引用变量--否则,你将一事无成。
例如
CLASS Person
INSTANCE Name, SSN
ACCESS Name CLASS Person
RETURN Name // 指变量名称
METHOD ShowName() CLASS Person
? Name // 指 ACCESS 方法
您还可以将 PROTECT 和 HIDDEN 变量与 ACCESS 方法结合使用。 通过定义与 PROTECT 或 HIDDEN 变量同名的访问方法,可以使用与类内部相同的语法从外部访问该变量。 然而,由于提前绑定的原因,内部引用总是直接引用变量。
当然,ACCESS 方法和实例变量不必同名。 这样做只是为了方便。 ACCESS <idVar> 使用的是方法的返回值。 因此,对于 PROTECT/HIDDEN 变量,可以使用不同名称的 ACCESS 方法。 例如
CLASS Person
PROTECT Name_Protected
ACCESS Name CLASS Person
RETURN Name_Protected
虚(拟)变量不是作为类的一部分定义的,而是由其他实例变量组成的。 换句话说,它是根据其他实例变量的值计算出来的变量。 与非导出(non-exported)实例变量一样,你可以使用常规方法来计算虚拟变量,但这意味着要使用不同的语法来访问它们。 ACCESS 方法将用于访问实例变量的语法扩展到了虚(拟)变量。
例如
CLASS Person
INSTANCE Name, SSN
ACCESS Name CLASS Person
RETURN Name
METHOD Init(cName, cSSN) CLASS Person
Name := cName
SSN := cSSN
ACCESS FullID CLASS Person
RETURN Name + SSN
FUNCTION UseClass()
LOCAL oFriend AS Person
oFriend := Person{"Bill Brown", "213-88-9546"}
? oFriend:Name // Bill Brown
? oFriend:FullID // Bill Brown213-88-9546
与非导出(non-exported)变量和 ACCESS 方法相比,EXPORT 变量使用起来更快、更方便,但使用 EXPORT 变量却有悖于封装性,而封装性正是为提高应用程序的完整性而应努力实现的。 使用 ACCESS 方法和 ASSIGN 方法,你可以在应用程序原型设计的早期阶段使用 EXPORT 变量,之后在不改变类接口的情况下使用方法保护变量。
ACCESS 是 METHOD 的一种特例,除了调用方式(即不带参数,就像实例变量一样)外,其行为与其他方法相同。 更多详情,请参阅本指南中的 METHOD 语句 。
注意:ACCESS 方法的内部引用如果没有相应的常规 INSTANCE 变量(例如,虚(拟)变量或对不同名称的 HIDDEN 或 PROTECT 变量的公共访问),则必须使用 SELF: 前缀。 内部引用是指来自类或其子类方法内部的引用。 如果系统没有找到实例变量,则会假定是内存变量(这可能会导致编译器出错,具体取决于编译器设置中是否选择了 "允许未声明变量"),除非使用了 SELF:,否则系统不会尝试将引用识别为 ACCESS 方法。
除了 XSharp 的非类型化方法实现外,现在还支持方法参数和返回值的强类型,这为您提供了一种可以获得高度稳定代码的机制。提供的类型信息使编译器能够执行必要的类型检查,从而保证更高的代码质量稳定性。
使用强类型方法的另一个好处是性能。强类型方法的实现假定当程序员使用强类型消息时,编译器可以有效地为相应的方法调用执行早期绑定(early binding)。由于这种实现,强类型方法的调用比相应的非类型化方法调用稍快。然而,这些优势是以失去非类型方法所提供的灵活性为代价的。
因此,重要的是要记住,在继承链中交替使用特定方法的强类型和非类型版本既不允许也不可能。
XSharp 允许对 METHODs、ACCESSes 和 ASSIGNs 进行强类型化。程序员通过以下两个步骤完成 XSharp 中强类型方法的指定:
1. | 在其相应的类中进行强类型方法的强制声明。 |
该声明负责在 XSharp 用于调用强类型方法的所谓虚拟表中声明方法的顺序。在子类中重新声明方法是不允许的,因为这会导致编译器的歧义。 |
2. | 定义强类型方法。 |
与强类型函数不同,方法类型化要求对方法参数、方法返回值进行强类型化,并指定有效的调用约定。 |
以下调用约定对强类型方法有效:STRICT、PASCAL 或 CALLBACK。 |
下面的示例使用 ACCESS 根据其他实例变量的值进行计算:
CLASS Rectangle
INSTANCE Length, Height AS INT
METHOD Init(nX, nY) CLASS Rectangle
Length := nX
Height := nY
RETURN SELF
ACCESS Area CLASS Rectangle
RETURN Length * Height
FUNCTION FindArea()
LOCAL oShape AS Rectangle
oShape := Rectangle{3, 4}
? oShape:Area // 显示: 12
ASSIGN, CLASS, METHOD, PROPERTY