Как можно автоматически инициализировать записи Delphi?

Чтобы инициализировать записи 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; 

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

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

Ответ 1

Вы можете использовать скрытое строковое поле (которое автоматически инициализируется пустой строкой), чтобы реализовать инициализацию "по времени" и неявные операторы, чтобы скрыть детали реализации. В приведенном ниже коде показано, как реализовать "двойное" поле, которое автоматически инициализируется на 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 и изменить значение по умолчанию.

Ответ 2

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

Ответ 3

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