Подтвердить что ты не робот

Есть ли способ обновить поле в записи, зная имя и значение поля

С учетом записи:

MyRecord = record
    Company: string;
    Address: string;
    NumberOfEmplyees: integer;

Вы можете написать вызов функции, например

function UpdateField(var FieldName: string; FieldValue: variant): bool;

чтобы:

UpdateField('Company', 'ABC Co');

обновит MyRecord.Company до "ABC Co"?

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

Спасибо, Чарльз

4b9b3361

Ответ 1

Вам нужны современные версии Delphi, чтобы делать то, о чем вы просите, не прибегая к ручному кодированию поисков, например. через таблицу.

Обновленный RTTI, представленный в Delphi 2010, может поддерживать то, что вы ищете, но в Delphi 7 ничего не будет сделано для записей.

Ответ 2

Что Delphi 7 RTTI знает и может быть извлечено из TypeInfo(aRecordType), это:

  • Имя типа записи;
  • Глобальный размер записи;
  • Смещение и тип каждой засчитанной переменной в записи (строка/вариант/widestring/dynamic array/другая вложенная запись, содержащая переменные с подсчетом).

Последняя информация необходима для освобождения памяти, используемой каждой переменной, подсчитанной по ссылке в записи, или копирования содержимого записи во время выполнения. Инициализация записи также выполняется либо в коде, сгенерированном компилятором (если запись создается в стеке), либо с помощью метода _InitializeRecord(), либо с глобальным заполнением до 0, когда создается класс или динамический массив.

Это то же самое для типов record и object, во всех версиях Delphi.

Вы можете заметить, что в современной версии Delphi есть ошибка (включая, по крайней мере, Delphi 2009 и 2010), которые иногда не создайте код для инициализации объектов в стеке. Вместо этого вам придется использовать запись, но она нарушит совместимость с предыдущей версией Delphi.: (

Вот структура, используемая для хранения данных RTTI:

type
  TFieldInfo = packed record
    TypeInfo: ^PDynArrayTypeInfo; // information of the reference-counted type
    Offset: Cardinal; // offset of the reference-counted type in the record
  end;
  TFieldTable = packed record
    Kind: byte;
    Name: string[0]; // you should use Name[0] to retrieve offset of Size field
    Size: cardinal;  // global size of the record = sizeof(aRecord)
    Count: integer;  // number of reference-counted field info
    Fields: array[0..0] of TFieldInfo; // array of reference-counted field info
  end;
  PFieldTable = ^TFieldTable;

Используя эти данные, вот, например, что вы можете сделать:

Например, здесь можно сравнить две записи одного и того же типа, используя этот RTTI:

/// check equality of two records by content
// - will handle packed records, with binaries (byte, word, integer...) and
// string types properties
// - will use binary-level comparison: it could fail to match two floating-point
// values because of rounding issues (Currency won't have this problem)
function RecordEquals(const RecA, RecB; TypeInfo: pointer): boolean;
var FieldTable: PFieldTable absolute TypeInfo;
    F: integer;
    Field: ^TFieldInfo;
    Diff: cardinal;
    A, B: PAnsiChar;
begin
  A := @RecA;
  B := @RecB;
  if A=B then begin // both nil or same pointer
    result := true;
    exit;
  end;
  result := false;
  if FieldTable^.Kind<>tkRecord then
    exit; // raise Exception.CreateFmt('%s is not a record',[Typ^.Name]);
  inc(PtrUInt(FieldTable),ord(FieldTable^.Name[0]));
  Field := @FieldTable^.Fields[0];
  Diff := 0;
  for F := 1 to FieldTable^.Count do begin
    Diff := Field^.Offset-Diff;
    if Diff<>0 then begin
      if not CompareMem(A,B,Diff) then
        exit; // binary block not equal
      inc(A,Diff);
      inc(B,Diff);
    end;
    case Field^.TypeInfo^^.Kind of
      tkLString:
        if PAnsiString(A)^<>PAnsiString(B)^ then
          exit;
      tkWString:
        if PWideString(A)^<>PWideString(B)^ then
          exit;
      {$ifdef UNICODE}
      tkUString:
        if PUnicodeString(A)^<>PUnicodeString(B)^ then
          exit;
      {$endif}
      else exit; // kind of field not handled
    end;
    Diff := sizeof(PtrUInt); // size of tkLString+tkWString+tkUString in record
    inc(A,Diff);
    inc(B,Diff);
    inc(Diff,Field^.Offset);
    inc(Field);
  end;
  if CompareMem(A,B,FieldTable.Size-Diff) then
    result := true;
end;

Итак, для вашей цели, что Delphi 7 RTTI может сообщить вам во время выполнения, является позицией каждой строки в записи. Используя приведенный выше код, вы можете легко создать функцию, используя индекс поля:

     procedure UpdateStringField(StringFieldIndex: integer; const FieldValue: string);

Но у вас просто нет необходимой информации для реализации вашего запроса:

  • Имена полей не сохраняются в RTTI (только имя типа глобальной записи и даже не всегда AFAIK);
  • Только поля с подсчетом отсчета имеют смещение, а не другие поля простого типа (например, integer/double...).

Если вам действительно нужна эта функция, единственным решением в Delphi 7 является использование не записей, а классов.

В Delphi 7, если вы создадите класс с опубликованными полями, у вас будет вся необходимая информация для всех опубликованных полей. Затем вы можете обновить такое опубликованное полевое содержимое. Это то, что выполняется во время выполнения VCL при несериализации содержимого .dfm в экземплярах класса или с помощью ORM-подхода.