Для инициализации записей Delphi я всегда добавлял метод (класс или объект), который будет инициализироваться с известными хорошими значениями по умолчанию. Delphi также позволяет определять «конструкторы» записей с параметрами, но вы не можете определить свой собственный «конструктор» без параметров.

TSomeRecord = record
 Value1: double;
 Value2: double;

 procedure Init;
end;

procedure TSomeRecord.Init;
begin
  Value1 := MaxDouble;
  Value2 := Pi;
end; 

Учитывая приведенную выше запись, нет предупреждения о том, что запись не была инициализирована. Разработчики могут не указывать Init для записи. Есть ли способ автоматически инициализировать записи по умолчанию, потенциально более чем простой FillChar;

Например

var 
  LSomeRecord: TSomeRecord
begin
  // someone forgot to call LSomeRecord.Init here
  FunctionThatTakesDefaultSomeRecord(LSomeRecord);
end; 

Как можно автоматически инициализировать запись до моих значений по умолчанию?

[Примечание] Я не хочу изменять проблему после того, как на нее был дан ответ. Всем читателям рекомендуется прочитать комментарии о лучших практиках использования методов класса для инициализации вместо метода изменяющегося объекта.

11
Jasper Schellingerhout 8 Сен 2016 в 16:57

3 ответа

Лучший ответ

Вы можете использовать скрытое строковое поле (которое автоматически инициализируется пустой строкой) для реализации инициализации «вовремя» и неявные операторы, чтобы скрыть детали реализации. В приведенном ниже коде показано, как реализовать «двойное» поле, которое автоматически инициализируется значением Pi.

program Project44;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils;

type
  TAutoDouble = record
  private
    FValue: double;
    FInitialized: string;
    procedure Initialize(const val: double = Pi);
  public
    class operator Implicit(const rec: TAutoDouble): double;
    class operator Implicit(const val: double): TAutoDouble;
  end;

  TSomeRecord = record
    Value1: TAutoDouble;
    Value2: TAutoDouble;
  end;

{ TAutoDouble }

procedure TAutoDouble.Initialize(const val: double);
begin
  if FInitialized = '' then begin
    FInitialized := '1';
    FValue := val;
  end;
end;

class operator TAutoDouble.Implicit(const rec: TAutoDouble): double;
begin
  rec.Initialize;
  Result := rec.FValue;
end;

class operator TAutoDouble.Implicit(const val: double): TAutoDouble;
begin
  Result.Initialize(val);
end;

var
  sr, sr1: TSomeRecord;

begin
  try
    Writeln(double(sr.Value1));
    Writeln(double(sr.Value2));
    sr.Value1 := 42;
    Writeln(double(sr.Value1));
    sr1 := sr;
    Writeln(double(sr.Value1));
    Writeln(double(sr.Value2));
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Однако нет хорошего способа сделать это решение более универсальным в отношении значения по умолчанию - если вам нужно другое значение по умолчанию, вам нужно клонировать определение / реализацию TAutoDouble и изменить значение по умолчанию.

4
gabr 8 Сен 2016 в 15:47

AFAIK вы не можете не прибегать к уловкам, которые того не стоят (возможно, используя поля интерфейса, которые гарантированно инициализируются).

3
Uli Gerhardt 8 Сен 2016 в 14:11

Это было бы неплохо ... но я думаю, вы можете использовать какую-нибудь фабрику? или просто скромный метод, возвращающий запись ...

-1
Derek Seymour 18 Июл 2019 в 04:38