Go Up to Classes and Objects Index
This topic describes the following material:
- Property access
- Array properties
- Index specifiers
- Storage specifiers
- Property overrides and redeclarations
- Class properties
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.
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:
property propertyName[indexes]: type index integerConstant specifiers;
- propertyName is any valid identifier.
- [indexes] is optional and is a sequence of parameter declarations separated by semicolons. Each parameter declaration has the form identifier1, ..., identifiern: type. For more information, see Array Properties, below.
- type must be a predefined or previously declared type identifier. That is, property declarations like property Num: 0..9 ... are invalid.
- the index integerConstant clause is optional. For more information, see Index Specifiers, below.
- specifiers is a sequence of read, write, stored, default (or nodefault), and implements specifiers. Every property declaration must have at least one read or write specifier.
Properties are defined by their access specifiers. Unlike fields, properties cannot be passed as var parameters, nor can the @ operator be applied to a property. The reason is that a property doesn't necessarily exist in memory. It could, for instance, have a read method that retrieves a value from a database or generates a random value.
Every property has a read specifier, a write specifier, or both. These are called access specifiers and they have the form:
where fieldOrMethod is the name of a field or method declared in the same class as the property or in an ancestor class.
- If fieldOrMethod is declared in the same class, it must occur before the property declaration. If it is declared in an ancestor class, it must be visible from the descendant; that is, it cannot be a private field or method of an ancestor class declared in a different unit.
- If fieldOrMethod is a field, it must be of the same type as the property.
- If fieldOrMethod is a method, it cannot be dynamic and, if virtual, cannot be overloaded. Moreover, access methods for a published property must use the default register calling convention.
- In a read specifier, if fieldOrMethod is a method, it must be a parameterless function whose result type is the same as the property's type. (An exception is the access method for an indexed property or an array property.)
- In a write specifier, if fieldOrMethod is a method, it must be a procedure that takes a single value or const parameter of the same type as the property (or more, if it is an array property or indexed property).
For example, given the declaration:
property Color: TColor read GetColor write SetColor;
the GetColor method must be declared as:
function GetColor: TColor;
and the SetColor method must be declared as one of these:
procedure SetColor(Value: TColor); procedure SetColor(const Value: TColor);
(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:
type THeading = 0..359; TCompass = class(TControl) private FHeading: THeading; procedure SetHeading(Value: THeading); published property Heading: THeading read FHeading write SetHeading; ... end;
Given this declaration, the statements:
if Compass.Heading = 180 then GoingSouth; Compass.Heading := 135;
if Compass.FHeading = 180 then GoingSouth; Compass.SetHeading(135);
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:
procedure TCompass.SetHeading(Value: THeading); begin if FHeading <> Value then begin FHeading := Value; Repaint; // update user interface to reflect new value end; end;
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 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:
property Objects[Index: Integer]: TObject read GetObject write SetObject; property Pixels[X, Y: Integer]: TColor read GetPixel write SetPixel; property Values[const Name: string]: string read GetValue write SetValue;
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:
function GetObject(Index: Integer): TObject; function GetPixel(X, Y: Integer): TColor; function GetValue(const Name: string): string; procedure SetObject(Index: Integer; Value: TObject); procedure SetPixel(X, Y: Integer; Value: TColor); procedure SetValue(const Name, Value: string);
An array property is accessed by indexing the property identifier. For example, the statements:
if Collection.Objects = nil then Exit; Canvas.Pixels[10, 20] := clRed; Params.Values['PATH'] := 'C:\BIN';
if Collection.GetObject(0) = nil then Exit; Canvas.SetPixel(10, 20, clRed); Params.SetValue('PATH', 'C:\BIN');
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:
type TStringArray = class public property Strings[Index: Integer]: string ...; default; ... end;
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 can be abbreviated to StringArray. 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 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:
type TRectangle = class private FCoordinates: array[0..3] of Longint; function GetCoordinate(Index: Integer): Longint; procedure SetCoordinate(Index: Integer; Value: Longint); public property Left: Longint index 0 read GetCoordinate write SetCoordinate; property Top: Longint index 1 read GetCoordinate write SetCoordinate; property Right: Longint index 2 read GetCoordinate write SetCoordinate; property Bottom: Longint index 3 read GetCoordinate write SetCoordinate; property Coordinates[Index: Integer]: Longint read GetCoordinate write SetCoordinate; ... end;
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:
Rectangle.Right := Rectangle.Left + 100;
Rectangle.SetCoordinate(2, Rectangle.GetCoordinate(0) + 100);
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:
property Name: TComponentName read FName write SetName stored False;
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:
property Tag: Longint read FTag write FTag default 0;
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:
type TAncestor = class ... protected property Size: Integer read FSize; property Text: string read GetText write SetText; property Color: TColor read FColor write SetColor stored False; ... end; type TDerived = class(TAncestor) ... protected property Size write SetSize; published property Text; property Color stored True default clBlue; ... end;
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:
type TAncestor = class ... property Value: Integer read Method1 write Method2; end; TDescendant = class(TAncestor) ... property Value: Integer read Method3 write Method4; end; var MyObject: TAncestor; ... MyObject := TDescendant.Create;
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:
- Another class var declaration
- A procedure or function (i.e. method) declaration (including class procedures and class functions)
- A property declaration (including class properties)
- A constructor or destructor declaration
- A visibility scope specifier (public, private, protected, published, strict private, and strict protected)
type TMyClass = class strict private class var // Note fields must be declared as class fields FRed: Integer; FGreen: Integer; FBlue: Integer; public // ends the class var block class property Red: Integer read FRed write FRed; class property Green: Integer read FGreen write FGreen; class property Blue: Integer read FBlue write FBlue; end;
You can access the above class properties with the code:
TMyClass.Red := 0; TMyClass.Blue := 0; TMyClass.Green := 0;