Свойства

Свойство внешне выглядит как поле класса, однако, при доступе к нему на чтение или запись позволяет выполнять некоторые действия. Свойство описывается в классе или записи следующим образом:

property Prop: тип read PropertyReader write PropertyWriter;

В качестве 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. Добавление этой проверки позволяет избежать лишней перерисовки кнопки в случае, если ее ширина не меняется.