DEFINE PROPERTY statement
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.
Note: This statement is applicable only when used in a class or interface definition (.cls
) file.Syntax
Use the following syntax to define a property that you can read only:
Use the following syntax to define a property that you can write only:
Use the following syntax to declare an interface property prototype:
Use the following syntax to declare an abstract property prototype:
[ PRIVATE | PROTECTED | PUBLIC ]
Specifies the access mode for the property. A PRIVATE property can be accessed only by the defining class. A PROTECTED property can be accessed by the defining class and any of its derived classes. A PUBLIC property can be accessed by: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 appropriateaccessor-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.[ STATIC ]
Defines a property that is a static member of the class type for which it is defined and that is scoped to the ABL session where it is referenced. ABL creates one copy of the specified class static property on first reference to the class type, and ABL creates only one such copy for any number of instances of the class that you create. You can reference an accessible static property in any piece of code.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.You can reference a private or protected instance property only in instance methods that are defined inside the same class or class hierarchy.Note: You cannot use a class instance that is not equal to the THIS-OBJECT system reference to reference a private or protected instance property that is defined in the same class, because PRIVATE and PROTECTED access modes are instance based in ABL. Thus, private and protected instance members are accessible only to other members of the same class instance, where as public instance members can be accessed from other instances of the same class, including the session “static instance” of the classFor 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:[ ABSTRACT ]
Defines the property as an abstract member of the class type for which it is defined. The defining class type must also be abstract. If you define an abstract property, it has the following requirements:
- You must specify the OVERRIDE option if an inherited abstract property has the same name and data type.
- You can specify either a PROTECTED or a PUBLIC access mode, depending on any inherited abstract property you might be overriding.
- The abstract property must be overridden and implemented in a derived class.
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.[ OVERRIDE ]
Specifies that this instance property overrides an abstract property inherited from an ABL or .NET abstract class.Note: A .NET abstract property or class is defined in C# with theabstract
keyword.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 theprimitive-type-name
andobject-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:property-name
Specifies the name of the property. Theproperty-name
must be unique among the names of all properties, events, and variable data members that are defined in the class and its inherited class hierarchy, and that are accessible to the defining class (not defined as PRIVATE in a super class).Note: Members of a class are grouped into six namespaces, including buffers/temp-tables, methods, variables/properties/events, ProDataSets, queries, and data-sources. Variables, properties, and events defined as members of a class share the same namespace. There can be only one class member in this namespace with a given name (not counting abstract member overrides).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.Note: If the property is defined in a class that is derived from a .NET class, other restrictions onproperty-name
apply. For more information, see the CLASS statement reference entry.ASprimitive-type-name
Specifies a built-in primitive type (primitive-type-name
) and an optional initial value (constant
) for the default memory of the property. The built-in data type can be one of the following:
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 23. (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 24. (For example, the AS data type, UNSIGNED-BYTE, indicates a .NET System.Byte.)Note: At run time, a property defined using anAS-data-type
keyword behaves in ABL like the corresponding ABL primitive type shown in Table 24. (For example, an UNSIGNED-BYTE behaves like an INTEGER.)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.Note: To manage public access for a complex data type that cannot be specified for a property, you can define both a data member of that complex type and a separate public method that passes the complex type as a parameter.AS [ CLASS ]object-type-name
object-type-name
Specifies the type name of an ABL or .NET class or interface. Specify an object type name using the syntax described in the Type-name syntax reference entry. With an appropriate USING statement, you can also specify a class or interface name alone, without the qualifying package or namespace.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).CLASS
For more information on object references, see the Class-based object reference reference entry.[ EXTENT [constant
]]
Defines the property as an array of data elements, where the element data type is specified by either the ASprimitive-type-name
option or the ASobject-type-name
option. This option can specify an array property as either determinate (has a defined number of elements) or indeterminate (has an undefined number of elements). To define a determinate array property, specify the EXTENT option with theconstant
argument. This optional argument is an integer value that represents the number of elements in the property array. To define an indeterminate array property, specify the EXTENT option without theconstant
argument.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:
- Initializing the array values when you define the property, using the INITIAL option
- Setting the number of elements in the array property using the EXTENT statement
- Assigning a determinate array to the indeterminate array, fixing it to the dimension of the determinate array
- Passing array parameters to a procedure, user-defined function, or class-based method, so that the indeterminate array property is the target for the passing of a determinate array, fixing the indeterminate array to the dimension of the determinate array
Once fixed, ABL treats a fixed indeterminate array as a determinate array.If you do not use the EXTENT option (or you specifyconstant
as 0), the property is not an array property.[ INITIAL {constant
| [constant
[ ,constant
] . . . ] } ]
The initial value of the property you want to define. If you use the ASprimitive-type-name
option and you do not use the INITIALconstant
option, the initial value is the default initial value for the data type of the variable or array element.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:
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.Table 35 lists the default initial values for the various property data types.
Table 32: Default variable initial values Data type Default initial value CHARACTER "" (an empty string) 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 (?
)
1If you display a class instance using the MESSAGE statement, ABL automatically invokes the ToString( ) method (provided by the Progress.Lang.Object class) on the object reference. To display a class instance in a frame (for example, using the DISPLAY statement), you must first explicitly convert the object reference to a displayable type using the INT64 function, the INTEGER function, the STRING function, or the ToString( ) method.
2You cannot use the INITIAL option to specify an initial value for this data type as part of the property definition.[ NO-UNDO ]
When the value of a property is changed during a transaction and the transaction is undone, the AVM restores the value of the property to its prior value. If you do not want, or if you do not need, the value of such a property to be undone even when it has been changed during a transaction, use the NO-UNDO option. NO-UNDO properties are more efficient; use this option whenever possible.[accessor-access-mode
]
Specifies the access mode for the immediately following accessor (GET or SET). The possible access modes include:
To use anaccessor-access-mode
with accessors:
- You must define at least one accessor (GET or SET) that defaults to the access mode of the property. Therefore, if you define only one accessor, you cannot specify a separate
accessor-access-mode
for it.- If you define two accessors, you can specify an
accessor-access-mode
for one of the accessors, but only if thisaccessor-access-mode
is more restrictive than the access mode of the property.- If you are defining an abstract property, you cannot specify the PRIVATE access mode for an accessor.
So, if the property access mode is PUBLIC, anyaccessor-access-mode
that you specify must be either PROTECTED or PRIVATE; if the property access mode is PROTECTED, anyaccessor-access-mode
that you specify must be PRIVATE, unless the property is abstract.GET [implementation
] .
An accessor (GET) that defines the property as readable. If you do not define a GET accessor for a class property, the property cannot be read, not even from within the defining class.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 animplementation
. 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:
array-index-parameter
Specifies an INPUT parameter that provides access to the index value of the current element of an array property from within theget-logic
. When a property is defined as an array using the EXTENT option, the GET accessor references the single element of the property array. Thearray-index-parameter
specifies the subscript value for the element being accessed. This is the syntax:
array-index-name
INTEGER | INT64
get-logic
Can contain ABL code that executes for any operation that reads the property from outside its own GET accessor definition. This code can include any ABL statements that are valid in a method of a class, including CATCH and FINALLY blocks, except statements that block for input. However, if you define the property itself as static, you cannot access any instance members of classes (including the defining class) or use the SUPER and THIS-OBJECT system references; you can only access static class members and the local data elements of the GET accessor. The actual value that you return from the property is entirely dependent on yourget-logic
code. Withinget-logic
, any operation that reads fromproperty-name
(such as the right-hand side of an assignment) directly reads the value of the property’s default memory. However, any operation that writes toproperty-name
withinget-logic
invokes the property’s own SET accessor to assign the value, exactly like writing the property from outside of its own definition. If the SET accessor is defined with an implementation, that implementation determines how the value is written to the property.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 fromget-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 inget-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 anarray-index-parameter
in this case is a compiler error.SET [implementation
] .
An accessor (SET) that defines the property as writable. If you do not define a SET accessor, the property cannot be written, not even from within the defining class. You can define a SET accessor using two basic forms: with or without an implementation.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 animplementation
. 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:
parameter-definition
Specifies an INPUT parameter that provides access to the value written to the property from within theset-logic
. Theset-logic
can contain ABL code that executes for any operation that writes to the property from outside its own SET accessor definition. This code can include any ABL statements that are valid in a method of a class, including CATCH and FINALLY blocks, except statements that block for input. However, if you define the property itself as static, you cannot access any instance members of classes (including the defining class) or use the SUPER and THIS-OBJECT system references; you can only access static class members and the local data elements of the SET accessor. To access the value being written to the property, specifyparameter-definition
using the following syntax:
INPUTparameter-name
AS {primitive-type-name
| [ CLASS ]object-type-name
}
array-index-parameter
Specifies an INPUT parameter that provides access to the index value of the current element of an array property from within theset-logic
. When a property is defined as an array using the EXTENT option, the SET accessor references a single element of the property array. Thearray-index-parameter
specifies the subscript value for the element being accessed. This is the syntax:
array-index-name
INTEGER | INT64
set-logic
Can contain ABL code that accesses the value written to the property asparameter-name
, then usesparameter-name
(if you choose) to set the new value for the property. The actual value that you use to set the property is entirely dependent on yourset-logic
code. Withinset-logic
, any operation that writes toproperty-name
(such as the left-hand side of an assignment) directly writes the specified value to the property’s default memory. However, any operation that reads fromproperty-name
withinset-logic
invokes the property’s own GET accessor to read the value, exactly like reading the property from outside of its own definition. If the GET accessor is defined with an implementation, that implementation determines the value that is read from the property.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 toproperty-name
withinset-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 throughparameter-name
.If the property is an array, the default accessor implementation handles the subscript references automatically. Providing anarray-index-parameter
in this case is a compiler error.Note: This is equivalent to, but more efficient than, using an implementation to explicitly assignproperty-name
the value ofparameter-name
.ExamplesThe 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 to set the property value. In this case, the class sets the property from data (RepName
field) in thesports2000
database provided by a buffer (bSalesRep
), which is PRIVATE.The class also provides a PUBLIC method (
getNextSalesRep( )
) to read theSalesRep
table one record at a time and set thecCurrentSalesRepName
property to the value of theRepName
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 thecCurrentSalesRepName
property to the Unknown value (?
) if theSalesRep
table is empty orgetNextSalesRep( )
reaches the end of theSalesRep
table.
The following procedure (
r-runDefineProperties1.p
) instantiates ther-DefineProperties1
class, referenced byclProps
, and reads and displays the value of theclProps:cCurrentSalesRepName
property in a message, starting with the firstSalesRep
record found as part of class instantiation. The procedure then displays the value ofclProps:cCurrentSalesRepName
in a message for each record found by theclProps:getNextSalesRep( )
method, restarting from the beginning of theSalesRep
table at the direction of the user.
In the next example, the
r-DefineProperties2
class defines a PUBLIC property (cNextSalesRepName
) that provides the same data as thecCurrentSalesRepName
property defined by ther-DefineProperties1
class. However, the GET accessor of thecNextSalesRepName
property is also used to provide the same access to theSalesRep
table that thegetNextSalesRep( )
method provides for ther-DefineProperties1
class. So, the value ofcNextSalesRepName
changes with each access.
Because this
cNextSalesRepName
property incorporates the record access provided by thegetNextSalesRep( )
method, the following procedure that accesses thecNextSalesRepName
property must also use the property in a manner similar to how ther-runDefineProperties1.p
procedure uses thegetNextSalesRep( )
method. As a result, the class also provides a second, publicly writable property (lSalesRepRestart
) to indicate when the reading ofSalesRep
records must restart from the beginning of the table. (Note that thegetNextSalesRep( )
method provides its own INPUT parameter to indicate whether to restart record reading.)Finally, the
r-DefineProperties2
class constructor setslSalesRepRestart
together with an initial read of thecNextSalesRepName
property in order to initialize the record buffer to the first record of the table and pass the associatedRepName
field value to the instantiating procedure as an OUTPUT parameter.Thus, the following procedure (
r-runDefineProperties2.p
) instantiates ther-DefineProperties2
class, referenced byclProps
, and reads and displays the data from theclProps:cNextSalesRepName
property in a manner similar to how ther-runDefineProperties1.p
procedure reads and displays the same data using thegetNextSalesRep( )
method. However, because theclProps:cNextSalesRepName
property always returns theRepName
field for the nextSalesRep
record in the table, ther-runDefineProperties2.p
procedure must provide a separate variable (cCurrentSalesRepName
) of its own, which provides the same function that thecCurrentSalesRepName
property provides for ther-DefineProperties1
class, which is to maintain a current value read from theRepName
field.
Thus, the logic of the
r-runDefineProperties2.p
procedure is almost identical to ther-runDefineProperties1.p
procedure, reading aclProps:cNextSalesRepName
property instead of invoking aclProps:getNextSalesRep( )
method in order to read an appropriate value from theSalesRep
table.For more examples of property definitions, including static and abstract properties, see the descriptions of
r-CustObj.cls
,r-CustObjStatic.cls
, andr-CustObjAbstract.cls
in the CLASS statement reference entry.Notes
- You cannot define a property within a procedure (internal or external) or within a method of a class (constructor or destructor). It can only be defined as a member of a class or as a prototype in an interface. For more information about declaring property prototypes in an interface, see the INTERFACE statement reference entry.
- PUBLIC and PROTECTED properties defined within a class definition (.
cls
) file maintain their characteristics throughout the inherited class hierarchy. Thus, you cannot shadow (override) properties in a subclass that are defined in a super class.- You must define at least one GET or SET accessor, or define both accessors, for the property to be valid.
- You never invoke the GET and SET accessors defined for a property directly. These methods are only invoked implicitly when you read (GET) or write (SET) the value of the property. For example, you can read or write the property by using the property in an Assignment (=) statement or by passing the property as a parameter to a method, procedure, or user-defined function. For information on reading and writing property values, see the Class-based property access reference entry. As an alternative, you can define a method in a class to read or write the value of a data member that you separately define in the same class.
- If the property name is an ABL reserved keyword, you might need to use the THIS-OBJECT system reference or Type-name syntax to reference the property. For more information on referencing properties, see the reference entry for Class-based property access.
- To avoid any unexpected performance impact when using the property, ensure that the body of any GET or SET accessor minimizes processing that might incur delay when referencing the property.
- You cannot specify formatting information in a property definition. You can control property formatting using the same default rules for formatting a variable or data member:
- Rely on the default formatting for the data type of the property. For more information, see the Data types reference entry.
- Add formatting information when you access the property in a statement (for example, using a DISPLAY statement).
- You can handle application errors in an a property accessor as in any ABL block. By executing a RETURN ERROR action at the block level or a THROW action at the block level with the presence of a ROUTINE-LEVEL ON ERROR UNDO, THROW statement, the AVM returns the ERROR condition to the statement that references the property and works much like an error raised by a method. If a RETURN ERROR also includes the option to return a character string value, or you set the ReturnValue property of a Progess.Lang.AppError object that you THROW, you can get this value using the RETURN-VALUE function following the statement that references the property or in a CATCH block that catches the Progress.Lang.AppError object. If the body of an accessor contains an UNDO block, any unhandled ERROR condition in that block undoes only the data within that block, according to the NO-UNDO setting of the data. The property value, itself, is not undone unless the property is defined without NO-UNDO. For more information, see OpenEdge Development: Object-oriented Programming.
- If ERROR is raised during execution of an Assignment (=) statement, the value on the left-hand side usually remains unchanged from its value prior to the assignment. However, if the left-hand side of the assignment is a property and its SET accessor invokes the RETURN ERROR statement (raising ERROR on the assignment), the value of the property can be changed. This is because the SET accessor is a method, and like all methods that raise ERROR, any data elements that the SET accessor changes retain their most recent values after ERROR is raised. Thus, if the SET accessor changes the property value before invoking RETURN ERROR, the property retains its most recent change in value after ERROR is raised on the assignment.
- If an ABL property that implements a property defined in a .NET interface is accessed from .NET and it raises ERROR out of the accessor block, ABL returns a .NET System.Exception to the caller. If the error is raised by executing a RETURN ERROR with the optional error string, the Message property of the System.Exception describes the operation where the error occurred, but the error string is available only to the ABL session, using the RETURN-VALUE function. If the error is raised by executing a RETURN ERROR with an optional ABL error object or by executing an UNDO, THROW, the System.Exception Message property includes both a description of the operation where the error occurred and any messages from the ABL error object. If the error is fatal, the AVM responds as for any ABL class, generating a protrace file and exiting the session.
See alsoAssignment (=) statement, Class-based property access, Data types, DEFINE VARIABLE statement, DISPLAY statement, METHOD statement, RETURN statement
OpenEdge Release 10.2B
|