Properties |
Top Previous Next |
A property, like a field, defines an attribute of an object. But while a field is merely a storage location whose contents can be examined and changed, a property associates specific actions with reading or modifying its data. Properties provide control over access to an object's attributes, and they allow attributes to be computed. Properties allow to encapsulate with a field-like syntax what can actually be methods (getter/setter). The declaration of a property specifies a name and a type, and includes at least one access specifier. The syntax of a property declaration is:
The following form property Name : Type; declares a property backed by a hidden (inaccessible) field. The field can optionally be initialized. The following form property Name; can be used to promote the visibility of a property without altering it in any other way, for instance to make public a property that was previously protected. Properties can also be prefixed by 'class' in which case they will be restricted to class variables, class methods and class properties, but can then be used on the class type (metaclass) and not just on instance.
Property Access
Every property has a read specifier, a write specifier, or both. For example, given the declaration:
the GetColor method must be declared as:
and the SetColor method must be declared as one of these:
(The name of SetColor's parameter, of course, doesn't have to be Value.) When a property is referenced in an expression, its value is read using the field or method listed in the read specifier. When a property is referenced in an assignment statement, its value is written using the field or method listed in the write specifier. The example below declares a class called TCompass with a published property called Heading. The value of Heading is read through the FHeading field and written through the SetHeading procedure:
Given this declaration, the statements:
correspond to:
In the TCompass class, no action is associated with reading the Heading property; the read operation consists of retrieving the value stored in the FHeading field. On the other hand, assigning a value to the Heading property translates into a call to the SetHeading method, which, presumably, stores the new value in the FHeading field as well as performing other actions. For example, SetHeading might be implemented like this:
A property whose declaration includes only a read specifier is a read-only property, and one whose declaration includes only a write specifier is a write-only property. It is an error to assign a value to a read-only property or use a write-only property in an expression.
Array Properties
Array properties are indexed properties. They can represent things like items in a list, child controls of a control, and pixels of a bitmap. The declaration of an array property includes a parameter list that specifies the names and types of the indexes. For example:
The format of an index parameter list is the same as that of a procedure's or function's parameter list, except that the parameter declarations are enclosed in brackets instead of parentheses. Unlike arrays, which can use only ordinal-type indexes, array properties allow indexes of any type.
For array properties, access specifiers must list methods rather than fields. The method in a read specifier must be a function that takes the number and type of parameters listed in the property's index parameter list, in the same order, and whose result type is identical to the property's type. The method in a write specifier must be a procedure that takes the number and type of parameters listed in the property's index parameter list, in the same order, plus an additional value or const parameter of the same type as the property.
For example, the access methods for the array properties above might be declared as:
An array property is accessed by indexing the property identifier. For example, the statements:
correspond to:
The definition of an array property can be followed by the default directive, in which case the array property becomes the default property of the class. For example:
If a class has a default property, you can access that property with the abbreviation object[index], which is equivalent to object.property[index]. For example, given the declaration above, StringArray.Strings[7] can be abbreviated to StringArray[7]. A class can have only one default property with a given signature (array parameter list), but it is possible to overload the default property. Changing or hiding the default property in descendent classes may lead to unexpected behavior, since the compiler always binds to properties statically.
Index Specifiers
Index specifiers allow several properties to share the same access method while representing different values. An index specifier consists of the directive index followed by an integer constant between -2147483647 and 2147483647. If a property has an index specifier, its read and write specifiers must list methods rather than fields. For example:
An access method for a property with an index specifier must take an extra value parameter of type Integer. For a read function, it must be the last parameter; for a write procedure, it must be the second-to-last parameter (preceding the parameter that specifies the property value). When a program accesses the property, the property's integer constant is automatically passed to the access method. Given the declaration above, if Rectangle is of type TRectangle, then:
corresponds to:
Storage Specifiers
The optional stored, default, and nodefault directives are called storage specifiers. They have no effect on program behavior, but control whether or not to save the values of published properties in form files. The stored directive must be followed by True, False, the name of a Boolean field, or the name of a parameterless method that returns a Boolean value. For example:
If a property has no stored directive, it is treated as if stored True were specified. The default directive must be followed by a constant of the same type as the property. For example:
To override an inherited default value without specifying a new one, use the nodefault directive. The default and nodefault directives are supported only for ordinal types and for set types, provided the upper and lower bounds of the set's base type have ordinal values between 0 and 31; if such a property is declared without default or nodefault, it is treated as if nodefault were specified. For reals, pointers, and strings, there is an implicit default value of 0, nil, and '' (the empty string), respectively.
Note: You can't use the ordinal value 2147483648 has a default value. This value is used internally to represent nodefault.
When saving a component's state, the storage specifiers of the component's published properties are checked. If a property's current value is different from its default value (or if there is no default value) and the stored specifier is True, then the property's value is saved. Otherwise, the property's value is not saved.
Note: Property values are not automatically initialized to the default value. That is, the default directive controls only when property values are saved to the form file, but not the initial value of the property on a newly created instance.
Storage specifiers are not supported for array properties. The default directive has a different meaning when used in an array property declaration. See Array Properties, above.
Property Overrides and Redeclarations
A property declaration that doesn't specify a type is called a property override. Property overrides allow you to change a property's inherited visibility or specifiers. The simplest override consists only of the reserved word property followed by an inherited property identifier; this form is used to change a property's visibility. For example, if an ancestor class declares a property as protected, a derived class can redeclare it in a public or published section of the class. Property overrides can include read, write,stored, default, and nodefault directives; any such directive overrides the corresponding inherited directive. An override can replace an inherited access specifier, add a missing specifier, or increase a property's visibility, but it cannot remove an access specifier or decrease a property's visibility. An override can include an implements directive, which adds to the list of implemented interfaces without removing inherited ones.
The following declarations illustrate the use of property overrides:
The override of Size adds a write specifier to allow the property to be modified. The overrides of Text and Color change the visibility of the properties from protected to published. The property override of Color also specifies that the property should be filed if its value isn't clBlue. A redeclaration of a property that includes a type identifier hides the inherited property rather than overriding it. This means that a new property is created with the same name as the inherited one. Any property declaration that specifies a type must be a complete declaration, and must therefore include at least one access specifier. Whether a property is hidden or overridden in a derived class, property look-up is always static. That is, the declared (compile-time) type of the variable used to identify an object determines the interpretation of its property identifiers. Hence, after the following code executes, reading or assigning a value to MyObject.Value invokes Method1 or Method2, even though MyObject holds an instance of TDescendant. But you can cast MyObject to TDescendant to access the descendent class's properties and their access specifiers:
Class Properties
Class properties can be accessed without an object reference. Class property accessors must themselves be declared as class static methods, or class fields. A class property is declared with the class property keywords. Class properties cannot be published, and cannot have stored or default value definitions.
You can introduce a block of class static fields within a class declaration by using the class var block declaration. All fields declared after class var have static storage attributes. A class var block is terminated by the following:
For example:
You can access the above class properties with the code:
|