In Object Pascal, a Property is a specialized language feature that allows an external user to access an object’s internal data using simple assignment syntax (Object.Property := Value), while internally executing complex code (methods) to control the reading and writing of that data.
This is the central mechanism for enforcing Encapsulation (P2.4).
1. The Role of Properties
In a purely OOP sense, external code should never directly access a field marked as private. Instead, interaction should occur via methods called Getters (to read data) and Setters (to write data).
| Direct Field Access (Bad) | Property Access (Good) |
MyObj.fName := 'Error'; | MyObj.Name := 'Valid Name'; |
| Bypasses any validation logic. | Runs the Setter method to validate the new value. |
Properties automate this method-based access using a clean syntax.
2. Property Declaration
A property is declared using the keywords property, followed by the data type, and mandatory read and optional write directives.
program PropertyDemo;
type
TUser = class(TObject)
private
// 1. Private Backing Field (often starts with 'f')
fAge: Integer;
// 2. Private Accessor Methods (Getter and Setter)
function GetAge: Integer;
procedure SetAge(const Value: Integer);
public
// 3. The Property Declaration
// read: specifies the Getter method to call when reading the property
// write: specifies the Setter method to call when writing the property
property Age: Integer read GetAge write SetAge;
end;
implementation
// ...
3. Implementing the Accessor Methods
A. The Getter (read)
The read directive specifies a function (the Getter) that is called when the property’s value is requested.
function TUser.GetAge: Integer;
begin
// The Getter often just returns the value of the private field
Result := fAge;
end;
B. The Setter (write)
The write directive specifies a procedure (the Setter) that is called when the property’s value is assigned. The new value is automatically passed to the setter procedure in a special parameter named Value.
The Setter is where you enforce rules, validation, or perform side effects (e.g., logging or updating the UI).
procedure TUser.SetAge(const Value: Integer);
begin
// Validation: Only update if the new value is sensible
if (Value >= 0) and (Value <= 120) then
begin
fAge := Value; // Update the private field
end
else
begin
// Optional: Raise an exception or log an error
Writeln('Error: Invalid age value attempted.');
end;
end;
4. Property Usage and Control
A. Full Read/Write Property
The full declaration gives external code complete, but controlled, access.
var
U: TUser;
begin
U := TUser.Create;
U.Age := 35; // Calls SetAge(35)
Writeln(U.Age); // Calls GetAge and prints the result
end.
B. Read-Only Property
To prevent external code from changing the value, omit the write directive. The property can only be initialized internally.
// Example: The user's read-only ID, generated internally
property UserID: Integer read fUserID;
// External usage:
// Writeln(U.UserID); // OK
// U.UserID := 5; // ERROR: Cannot assign to read-only property
C. Write-Only Property
A property that can be written to, but not read back. This is rare, but sometimes used for one-time initialization or secure input (like passwords).
property Password: String write SetPassword;
5. Array Properties (Accessing Internal Collections)
A property can also provide controlled access to elements within an internal array or collection using an index, much like an array itself. This is done with the index directive on the accessor methods.
type
TStringList = array of String;
TContainer = class
private
fStrings: TStringList;
function GetString(Index: Integer): String;
procedure SetString(Index: Integer; const Value: String);
public
// This property allows array-like access via an index
property Strings[Index: Integer]: String read GetString write SetString;
end;
// External usage:
// C.Strings[5] := 'New Value'; // Calls SetString(5, 'New Value')
