Есть ли способ обновить поле в записи, зная имя и значение поля
С учетом записи:
MyRecord = record
Company: string;
Address: string;
NumberOfEmplyees: integer;
Вы можете написать вызов функции, например
function UpdateField(var FieldName: string; FieldValue: variant): bool;
чтобы:
UpdateField('Company', 'ABC Co');
обновит MyRecord.Company до "ABC Co"?
Я искал пример, но все, что я нашел, для базы данных. Любая помощь, указывающая мне в правильном направлении, ценится.
Спасибо,
Чарльз
Ответы
Ответ 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-подхода.