Свойство внешне выглядит как поле класса, однако, при доступе к нему на чтение или запись позволяет выполнять некоторые действия. Свойство описывается в классе или записи следующим образом:
property Prop:
тип
readPropertyReader
writePropertyWriter
;
В качестве
может
выступать: PropertyReader
В качестве
может
выступать: PropertyWriter
Одна из секций - read или write - может быть опущена, в этом случае мы имеем свойство с доступом только на запись или только на чтение соответственно.
При доступе к свойству на чтение вызывается
, при доступе на запись -
PropertyReader
.PropertyWriter
Как правило, каждое свойство связано с некоторым полем класса и возвращает значение этого поля с помощью функции чтения, а меняет - с помощью процедуры записи. Функция чтения и процедура записи должны быть методами этого класса и иметь следующий вид:
function getProp:
тип
;
procedure setProp(value:тип
);
Если функция чтения свойства просто возвращает значение некоторого поля, то вместо её имени можно указать имя этого поля. Аналогично если процедура записи просто присваивает значение некоторому полю, то вместо её имени можно указать имя этого поля.
Любая из секций read
или write
может быть опущена, в этом
случае мы получаем свойство с доступом только на запись или с доступом только на чтение.
Обычно функция чтения и процедура записи описываются в приватной секции класса. Они могут быть виртуальными, в этом случае их уместно описывать в защищенной секции класса.
Для начала рассмотрим пример, в котором используются функция чтения и процедура записи свойства:
type
Person = class
private
fName: string;
fAge: integer;
procedure setAge(value: integer);
begin
if value<0 then
value := 0;
fAge := value
end;
function getAge: integer;
begin
Result := fAge;
end;
function getName: string;
begin
Result := fName;
end;
function getId: string;
begin
Result := fName + fAge.ToString;
end;
public
constructor (name: string; age: integer);
begin
fName := name;
fAge := age;
end;
property Age: integer read getAge write setAge;
property Name: string read getName;
property Id: string read getId;
end;
begin
var p: Person;
p := new Person('Иванов',20);
p.Age := -3; // p.Age = 0 !
p.Age := p.Age + 1; // компилятор заменяет этот код на p.setAge(p.getAge + 1);
writeln(p.Id);
end.
Всякий раз, когда мы присваиваем свойству Age
новое значение,
вызывается процедура setAge
с
соответствующим параметром. Всякий раз, когда мы считываем значение свойства
Age
, вызывается функция getAge
.
Как уже отмечалось, в тривиальных случаях имя процедуры в секции write
свойства и имя функции в секции read
свойства можно
заменить на имена соответствующих полей. Приведём код с учётом этого замечания:
type
Person = class
private
fName: string;
fAge: integer;
procedure setAge(value: integer);
begin
if value<0 then
value := 0;
fAge := value
end;
function getId: string;
begin
Result := fName + fAge.ToString;
end;
public
constructor (name: string; age: integer) := (fName,fAge) := (name,age);
property Age: integer read fAge write setAge;
property Name: string read fName;
property Id: string read getId;
end;
Наконец, воспользуемся расширенными свойствами, заменив getId
на выражение fName + fAge.ToString
, а setAge
- на
оператор, реализующий тело этой процедуры:
type
Person = class
private
fName: string;
fAge: integer;
public
constructor (name: string; age: integer) := (fName,fAge) := (name,age);
property Age: integer read fAge write fAge := value<0 ? 0 : value;
property Name: string read fName;
property Id: string read fName + fAge.ToString;
end;
Заметим, что расширенные свойства имеются только в PascalABC.NET, введены в язык чтобы не писать отдельно функцию чтения или процедуру записи и отсутстуют в других версиях Паскаля.
Расширенные свойства удобно использовать во многих ситуациях. Например, когда обычные свойства рализованы обращением к аналогичным свойствам поля этого класса:
type
MyList<T> = class
private
l := new List<T>;
public
property Capacity: integer read l.Capacity write l.Capacity := value;
end;
begin
var ml := new MyList<integer>;
ml.Capacity := 5; // доступ на запись: значение 5 копируется в переменную value
Println(ml.Capacity); // доступ на чтение
end.
Свойства не могут передаваться по ссылке в процедуры и функции. Например, следующий код ошибочен:
Inc(p.Age); // ошибка!
Если требуется обработать значение свойства, передав его по ссылке, то надо воспользоваться вспомогательной переменной:
a := p.Age;
Inc(a);
p.Age := a;
Однако, свойства соответствующих типов можно использовать в левой части
операций присваивания += -= *= /=
:
p.Age += 1;
Свойства очень удобны при работе с визуальными объектами,
поскольку позволяют автоматически перерисовывать объект, если изменить
какие-либо его визуальные характеристики. Например, если создана
кнопка b1
типа
Button
,
то для визуального изменения ее ширины достаточно присвоить значение ее свойству
Width
:
b1.Width := 100;
Процедура для записи этого свойства в приватное поле fWidth
будет выглядеть примерно так:
procedure SetWidth(w: integer);
код перерисовки кнопки
begin
if (w>0) and (w<>fWidth) then
begin
fWidth := w;
end
end;
Следует обратить внимание на вторую часть условия в операторе
if
: w<>fWidth
.
Добавление этой проверки позволяет избежать лишней перерисовки кнопки в случае,
если ее ширина не меняется.