Defines a property of a class, declares a property prototype in an ABL interface, or overrides an abstract property inherited from an ABL or .NET abstract super class. A property is a named member of a class that allows you to read or write a value using the same syntax as a data member. However, a property can define special methods (accessors) that specify if and how the property can be read or written. The following description begins with the syntax for defining a property that you can both read and write.
DEFINE [ PRIVATE | PROTECTED | PUBLIC ][ STATIC | ABSTRACT ] [ OVERRIDE ][ SERIALIZABLE ] PROPERTY property-name {{ AS primitive-type-name| AS [ CLASS ]object-type-name } [ EXTENT [constant ] ]} [ INITIAL { constant | {[constant[ , constant]...]}}] [ NO-UNDO ] { [accessor-access-mode] GET [implementation] . SET [implementation] . | GET [implementation] . [accessor-access-mode] SET [implementation] . } |
Use the following syntax to define a property that you can read only:
DEFINE [ PRIVATE | PROTECTED | PUBLIC ][ STATIC | ABSTRACT ] [ OVERRIDE ] PROPERTY property-name {{ AS primitive-type-name| AS [ CLASS ]object-type-name }[ EXTENT [constant ]]} [ INITIAL { constant |{[constant[ , constant]...]}}] [ NO-UNDO ] GET [implementation] . |
Use the following syntax to define a property that you can write only:
DEFINE [ PRIVATE | PROTECTED | PUBLIC ][ STATIC | ABSTRACT ] [ OVERRIDE ] PROPERTY property-name {{ AS primitive-type-name| AS [ CLASS ]object-type-name }[ EXTENT [constant ]]} [ INITIAL { constant |{[[ , constant]...]}}] [ NO-UNDO ] SET [implementation] . |
Use the following syntax to declare an interface property prototype:
DEFINE [ PUBLIC ] PROPERTY property-name {{ AS primitive-type-name| AS [ CLASS ]object-type-name} [ EXTENT [constant ]][ NO-UNDO ]} { GET. SET. | GET. | SET. } |
Use the following syntax to declare an abstract property prototype:
DEFINE [ PROTECTED | PUBLIC ][ OVERRIDE ] ABSTRACT PROPERTY property-name {{ AS primitive-type-name| AS [ CLASS ]object-type-name} [ EXTENT [constant ]][ NO-UNDO ]} { GET. SET. | GET. | SET. } |
Any piece of code can access a PUBLIC static property. The default access mode is PUBLIC.
This access mode applies to both reading and writing the property value by default. However, you can set a different access mode for reading or writing the property value (but not both) using an appropriate accessor-access-mode option to define the corresponding accessor (GET or SET).
When declaring an interface property prototype, the access mode for the property must be PUBLIC (the default).
When defining an abstract property, the access mode for the property cannot be PRIVATE.
Without this option, ABL defines an instance property that is scoped to a single instance of the class where it is defined. ABL creates one copy of the specified instance property for each such class instance that you create. You can reference any public instance property (abstract or non-abstract) in any procedure, or in any instance or static method defined inside or outside of the class where the instance property is defined. Any static method can reference the public instance property only using an object reference to a class instance that defines the property as a member. If the referencing static method is defined in the same class as the public instance property, the class must instantiate itself in order to have access to an instance reference.
For more information on the mechanism for accessing properties of different access modes and scopes, see the reference entry for Class-based property access.
The STATIC option is not valid when you:
Any class definition for an instance property that includes the OVERRIDE option and does not include the ABSTRACT option defines a property implementation. If it also includes the ABSTRACT option, the inherited abstract property remains abstract.
This option is not valid either when you define a static property or when you declare an interface property prototype.
When you specify OVERRIDE, the property definition must match the inherited abstract property with respect to name, data type (including any EXTENT), specified accessors (GET or SET), and NO-UNDO setting. In addition, the access mode must not be more restrictive than the access mode defined for the overridden property. When overriding a .NET abstract property, the data type must also map appropriately to the inherited .NET property data type. For more information, see the description of the primitive-type-name and object-type-name options.
If you specify the ABSTRACT option, your overriding property is also defined as abstract, and it must be implemented in a class derived from the defining class. Note that you do not have to override an inherited abstract property that you want to remain abstract as long as the inheriting class is also abstract. However, doing so allows you to specify a less restrictive access mode for the abstract property.
If you do not specify the ABSTRACT option, your property definition implements the inherited abstract property.
This option is not valid:
You can reference this name from outside the property definition in order to read a value from or write a value to the property. You can reference this name from inside the property definition (in a GET or SET accessor) in order to read or write the value of the default memory for the property, according to its data type.
CHARACTER | COM-HANDLE | DATE | DATETIME | DATETIME-TZ | DECIMAL | HANDLE | INT64 | INTEGER| LOGICAL | LONGCHAR | MEMPTR | RAW | RECID | ROWID | AS-data-type |
If you are overriding a .NET abstract property or implementing a property defined in a .NET interface, primitive-type-name must specify the exact same .NET mapped data type that is defined for the corresponding .NET property. For a .NET mapped data type that is a default match for a given ABL primitive type, you must use the default matching ABL data type, as shown in Table 4. (For example, INTEGER indicates a .NET System.Int32.) For a .NET mapped data type that is not a default match for one of the ABL primitive types, ABL provides a data type keyword (AS-data-type) that you must use to explicitly indicate the required .NET data type, as shown in Table 5. (For example, the AS data type, UNSIGNED-BYTE, indicates a .NET System.Byte.)
Also note that when implementing a .NET array property, you must specify the .NET array object type (for example, "System.Int32[]" or "System.Byte[]"); you cannot use an ABL array equivalent (such as INTEGER EXTENT or UNSIGNED-BYTE EXTENT).
These are the same set of primitive types that can be specified for the return type of a method. Thus, for example, a property cannot have a complex type such as a temp-table, ProDataSet, or any other data type that is invalid as a method return type. For more information on these primitive types, see the Data types reference entry.
You cannot directly specify the type name of a .NET mapped object type (such as System.Int32). To define a property that matches a .NET mapped type, you must define it as the corresponding ABL primitive type (primitive-type-name).
If you are overriding a .NET abstract property or implementing a property defined in a .NET interface, object-type-name must specify the exact same .NET object type as the corresponding interface property. However, for .NET inner (nested) type, note the difference in the ABL syntax, which replaces the corresponding period (.) in the .NET object type with a plus (+) (see the Type-name syntax reference entry).
Also note that when implementing a .NET array property, you must specify the exact .NET array object type (for example, "System.Drawing.Point[]"); you cannot use an ABL array equivalent (such as System.Drawing.Point EXTENT).
For more information on object references, see the Class-based object reference reference entry.
The EXTENT is part of the property data type. For more information, see the Type-name syntax reference entry.
An indeterminate array property can be in one of two states: fixed or unfixed, meaning it either has a fixed dimension or it does not. An indeterminate array property has an unfixed dimension when first defined. You can fix the dimension of an indeterminate array property by:
Once fixed, ABL treats a fixed indeterminate array as a determinate array.
If you do not use the EXTENT option (or you specify constant as 0), the property is not an array property.
When you define an array property, you can supply initial values for each element in the array. For example:
If you do not supply enough values to fill up the elements of the array, the AVM puts the last value you named into the remaining elements of the array. If you supply too many values, the AVM raises an error.
If you define a property as an indeterminate array, and you supply initial values for elements in the array, the AVM fixes the number of elements in the array and treats the fixed indeterminate array as a determinate array. For example, the arrays defined by the following statements are equivalent:
DEFINE VARIABLE x AS INTEGER NO-UNDO EXTENT INITIAL [1,2,3]. DEFINE VARIABLE x1 AS INTEGER NO-UNDO EXTENT 3 INITIAL [1,2,3]. |
You can also use the EXTENT statement to fix the number of elements in an unfixed indeterminate array variable. For more information, see the EXTENT statement reference entry.
The INITIAL option is not valid either when you define an abstract property or when you declare an interface property prototype.
The following table lists the default initial values for the various property data types.
Data type | Default initial value |
---|---|
CHARACTER | "" (an empty string) |
CLASS1, 2 | Unknown value (?) |
COM-HANDLE2 | Unknown value (?) |
DATE | Unknown value (?) (displays as blanks) |
DATETIME | Unknown value (?) |
DATETIME-TZ | Unknown value (?) |
DECIMAL | 0 |
HANDLE2 | Unknown value (?) |
INT64 | 0 |
INTEGER | 0 |
LOGICAL | no |
LONGCHAR | Unknown value (?) |
MEMPTR2 | A zero-length sequence of bytes |
RAW2 | A zero-length sequence of bytes |
RECID | Unknown value (?) |
ROWID2 | Unknown value (?) |
To use an accessor-access-mode with accessors:
So, if the property access mode is PUBLIC, any accessor-access-mode that you specify must be either PROTECTED or PRIVATE; if the property access mode is PROTECTED, any accessor-access-mode that you specify must be PRIVATE, unless the property is abstract.
It is valid to omit the GET accessor when defining a property for an interface. It is then up to the class implementing the interface to decide whether to define the GET accessor for the property or not. However, as stated above, if you do not define a GET accessor for a class property, the property cannot be read. If you do not define a GET accessor, then you must define a SET accessor.
You can define a GET accessor using two basic forms: with or without an implementation. However, if you are defining an abstract property or an interface property prototype, you cannot define the GET accessor with an implementation. This is the syntax for a GET accessor with an implementation:
Where supported, an unsubscripted array reference is a reference to the whole array. For a property array, an unsubscripted read reference invokes the GET accessor for each array element in ascending order by subscript value.
You do not have to use the property's default memory to provide the value read from a property. You can also use any other accessible data, such as a data member, as storage to access property values. To return a value from get-logic to any outside reader of the property, you must return a value with the correct data type using the RETURN statement (similar to any method that returns a value). If you do not invoke the RETURN statement in get-logic, the property returns the Unknown value (?).
This is the syntax for a GET accessor without an implementation:
Without an implementation, an operation that reads the property directly accesses the current value in the property's default memory.
If the property is an array, the default accessor implementation handles the subscript references automatically. Providing an array-index-parameter in this case is a compiler error.
It is valid to omit the SET accessor when defining a property for an interface. It is then up to the class implementing the interface to decide whether to define the SET accessor for the property or not. However, as stated above, if you do not define a SET accessor for a class property, the property cannot be written. If you do not define a SET accessor, then you must define a GET accessor.
You can define a SET accessor using two basic forms: with or without an implementation. However, if you are defining an abstract property or an interface property prototype, you cannot define the SET accessor with an implementation. This is the syntax for a SET accessor with an implementation:
Where supported, an unsubscripted array reference is a reference to the whole array. For a property array, an unsubscripted write reference invokes the SET accessor for each array element in ascending order by subscript value.
You do not have to use the property's default memory to store the property value. You can use any other accessible data, such as a data member, as storage to store property values. If you do not write a value to property-name within set-logic, the property's default memory retains whatever value it had prior to any operation that writes to the property.
This is the syntax for a SET accessor without an implementation:
Without an implementation, any operation that writes the property writes the value directly to the property's default memory without passing through parameter-name.
If the property is an array, the default accessor implementation handles the subscript references automatically. Providing an array-index-parameter in this case is a compiler error.
The examples that follow show two different ways to access the same private data of a class using properties.
The first example, shows a class (r-DefineProperties1) that defines a PUBLIC property (cCurrentSalesRepName) followed by a procedure that accesses this property. The property is defined with two accessors without implementations, providing direct access to the property value. The GET accessor is PUBLIC, but the SET accessor is PRIVATE, allowing only the class or an instance of the class to set the property value. In this case, the class sets the property from data (RepName field) in the sports2000 database provided by a buffer (bSalesRep), which is PRIVATE.
The class also provides a PUBLIC method (getNextSalesRep( )) to read the SalesRep table one record at a time and set the cCurrentSalesRepName property to the value of the RepName field for each record. This PUBLIC method also uses a PRIVATE method (restartSalesRep( )) to reset the record position to the beginning of the table, based on an INPUT parameter. The class constructor also uses this PRIVATE method to initialize the record buffer to the first record in the table. The class sets the cCurrentSalesRepName property to the Unknown value (?) if the SalesRep table is empty or getNextSalesRep( ) reaches the end of the SalesRep table.
r-DefineProperties1.cls
CLASS r-DefineProperties1: DEFINE PUBLIC PROPERTY cCurrentSalesRepName AS CHARACTER NO-UNDO GET. PRIVATE SET. DEFINE PRIVATE BUFFER bSalesRep FOR SalesRep. CONSTRUCTOR PUBLIC r-DefineProperties1 (): restartSalesRep() NO-ERROR. IF ERROR-STATUS:ERROR THEN RETURN ERROR ERROR-STATUS:GET-MESSAGE(1). END CONSTRUCTOR. METHOD PRIVATE VOID restartSalesRep (): FIND FIRST bSalesRep NO-ERROR. IF NOT AVAILABLE bSalesRep THEN DO: cCurrentSalesRepName = ?. RETURN ERROR "SalesRep table empty". END. ELSE cCurrentSalesRepName = bSalesRep.RepName. END METHOD. METHOD PUBLIC VOID getNextSalesRep (INPUT lRestart AS LOGICAL): IF lRestart THEN DO: restartSalesRep() NO-ERROR. IF NOT AVAILABLE bSalesRep THEN RETURN ERROR ERROR-STATUS:GET-MESSAGE (1). END. ELSE DO: FIND NEXT bSalesRep NO-ERROR. IF NOT AVAILABLE bSalesRep THEN cCurrentSalesRepName = ?. ELSE cCurrentSalesRepName = bSalesRep.RepName. END. END METHOD. END CLASS. |
The following procedure (r-runDefineProperties1.p) instantiates the r-DefineProperties1 class, referenced by clProps, and reads and displays the value of the clProps:cCurrentSalesRepName property in a message, starting with the first SalesRep record found as part of class instantiation. The procedure then displays the value of clProps:cCurrentSalesRepName in a message for each record found by the clProps:getNextSalesRep( ) method, restarting from the beginning of the SalesRep table at the direction of the user.
r-runDefineProperties1.p
DEFINE VARIABLE clProps AS CLASS r-DefineProperties1 NO-UNDO. clProps = NEW r-DefineProperties1() NO-ERROR. IF ERROR-STATUS:ERROR THEN DO: MESSAGE ERROR-STATUS:GET-MESSAGE(1) VIEW-AS ALERT-BOX. QUIT. END. DO WHILE NOT clProps:cCurrentSalesRepName = ?: MESSAGE"The current sales rep is " clProps:cCurrentSalesRepName + ". Continue?" VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE lContinue AS LOGICAL. IF NOT lContinue THEN LEAVE. ELSE DO: clProps:getNextSalesRep(FALSE). IF clProps:cCurrentSalesRepName = ? THEN DO: MESSAGE "End of sales rep list. Restart?" VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE lRestart AS LOGICAL. IF lRestart THEN clProps:getNextSalesRep(TRUE). END. END. END. |
In the next example, the r-DefineProperties2 class defines a PUBLIC property (cNextSalesRepName) that provides the same data as the cCurrentSalesRepName property defined by the r-DefineProperties1 class. However, the GET accessor of the cNextSalesRepName property is also used to provide the same access to the SalesRep table that the getNextSalesRep( ) method provides for the r-DefineProperties1 class. So, the value of cNextSalesRepName changes with each access.
r-DefineProperties2.cls
CLASS r-DefineProperties2: DEFINE PUBLIC PROPERTY lSalesRepRestart AS LOGICAL NO-UNDO PRIVATE GET. SET. DEFINE PRIVATE BUFFER bSalesRep FOR SalesRep. DEFINE PUBLIC PROPERTY cNextSalesRepName AS CHARACTER GET (): IF lSalesRepRestart THEN DO: restartSalesRep() NO-ERROR. IF NOT AVAILABLE(bSalesRep) THEN RETURN ERROR ERROR-STATUS:GET-MESSAGE(1). END. ELSE DO: FIND NEXT bSalesRep NO-ERROR. IF NOT AVAILABLE(bSalesRep) THEN cNextSalesRepName = ?. ELSE cNextSalesRepName = bSalesRep.RepName. END. RETURN cNextSalesRepName. END GET. PRIVATE SET. CONSTRUCTOR PUBLIC r-DefineProperties2 (OUTPUT cFirstSalesRepName AS CHARACTER): ASSIGN lSalesRepRestart = TRUE cFirstSalesRepName = cNextSalesRepName. END CONSTRUCTOR. METHOD PRIVATE VOID restartSalesRep (): lSalesRepRestart = FALSE. FIND FIRST bSalesRep NO-ERROR. IF NOT AVAILABLE bSalesRep THEN DO: cNextSalesRepName = ?. RETURN ERROR "SalesRep table is empty". END. ELSE cNextSalesRepName = bSalesRep.RepName. END METHOD. END CLASS. |
Because this cNextSalesRepName property incorporates the record access provided by the getNextSalesRep( ) method, the following procedure that accesses the cNextSalesRepName property must also use the property in a manner similar to how the r-runDefineProperties1.p procedure uses the getNextSalesRep( ) method. As a result, the class also provides a second, publicly writable property (lSalesRepRestart) to indicate when the reading of SalesRep records must restart from the beginning of the table. (Note that the getNextSalesRep( ) method provides its own INPUT parameter to indicate whether to restart record reading.)
Finally, the r-DefineProperties2 class constructor sets lSalesRepRestart together with an initial read of the cNextSalesRepName property in order to initialize the record buffer to the first record of the table and pass the associated RepName field value to the instantiating procedure as an OUTPUT parameter.
Thus, the following procedure (r-runDefineProperties2.p) instantiates the r-DefineProperties2 class, referenced by clProps, and reads and displays the data from the clProps:cNextSalesRepName property in a manner similar to how the r-runDefineProperties1.p procedure reads and displays the same data using the getNextSalesRep( ) method. However, because the clProps:cNextSalesRepName property always returns the RepName field for the next SalesRep record in the table, the r-runDefineProperties2.p procedure must provide a separate variable (cCurrentSalesRepName) of its own, which provides the same function that the cCurrentSalesRepName property provides for the r-DefineProperties1 class, which is to maintain a current value read from the RepName field.
r-runDefineProperties2.p
DEFINE VARIABLE clProps AS CLASS r-DefineProperties2 NO-UNDO. DEFINE VARIABLE cCurrentSalesRepName AS CHARACTER NO-UNDO. clProps = NEW r-DefineProperties2(OUTPUT cCurrentSalesRepName) NO-ERROR. DO WHILE NOT cCurrentSalesRepName = ?: MESSAGE "The current sales rep is " cCurrentSalesRepName + ". Continue?" VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE lContinue AS LOGICAL. IF NOT lContinue THEN LEAVE. ELSE DO: cCurrentSalesRepName = clProps:cNextSalesRepName. IF cCurrentSalesRepName = ? THEN DO: MESSAGE "End of sales rep list. Restart?" VIEW-AS ALERT-BOX QUESTION BUTTONS YES-NO UPDATE lRestart AS LOGICAL. IF lRestart THEN ASSIGN clProps:lSalesRepRestart = TRUE cCurrentSalesRepName = clProps:cNextSalesRepName. END. END. END. |
Thus, the logic of the r-runDefineProperties2.p procedure is almost identical to the r-runDefineProperties1.p procedure, reading a clProps:cNextSalesRepName property instead of invoking a clProps:getNextSalesRep( ) method in order to read an appropriate value from the SalesRep table.
For more examples of property definitions, including static and abstract properties, see the descriptions of r-CustObj.cls, r-CustObjStatic.cls, and r-CustObjAbstract.cls in the CLASS statement reference entry.