2011-06-22 12 views
19

otrzymał rekord:Czy istnieje sposób, aby zaktualizować pola w rekordzie znając nazwę i wartość pola

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

można napisać wywołanie funkcji jak

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

tak, aby:

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

zaktualizowałoby MyRecord.Company do "ABC Co"?

Szukałem przykładu, ale wszystko, co znalazłem, dotyczy bazy danych. Każda pomoc wskazująca mi we właściwym kierunku jest doceniana.

Dzięki, Charles

+4

+1 To bardzo dobre i jednoznacznie zadane pytanie. –

+0

Ale jeśli istnieje potrzeba aktualizacji takich pól, to może dane powinny być przechowywane w sposób umożliwiający takie aktualizacje. Mam na myśli, że możesz pracować z polami zapisanymi jako łańcuchy w formacie podobnym do 'Name = Value', a do tego użyj TStringList lub TIniFile. czego mi brakuje? –

+0

@Charles Czy przyjmiesz odpowiedź? Czy masz jeszcze więcej kłopotów? –

Odpowiedz

6

Trzeba nowoczesne wersje Delphi zrobić, o co prosisz, bez uciekania się do ręcznego kodowania wyszukiwań, na przykład przez stół.

Zaktualizowana wersja RTTI wprowadzona w Delphi 2010 może wspierać to, czego szukasz, ale nie ma nic w Delphi 7, które zrobi to dla rekordów.

+0

Bałem się tego. Dzięki za twoją pomoc. – Charles

+1

@mason dzięki za poprawkę. Czy mają tu znaczek RTTI delphi? Powinieneś go mieć, jeśli istnieje! Ts! –

10

Co Delphi 7 RTTI wie i może być pobrana z TypeInfo(aRecordType) jest:

  • Nazwa typ rekordu;
  • Rekordowy rozmiar globalny;
  • Przesunięcie i typ każdej zmiennej zliczanej referencyjnie w rekordzie (łańcuch/wariant/najszerszy/tablica dynamiczna/inny rekord zagnieżdżony zawierający zmienne zliczane odnie sieniami).

Najnowsze informacje są niezbędne, aby zwolnić pamięć używaną przez każdą zmienną zliczaną przez odniesienie wewnątrz rekordu lub skopiować zawartość rekordu w czasie wykonywania. Inicjowanie rekordu jest również wykonywane w kodzie wygenerowanym przez kompilator (jeśli rekord jest tworzony na stosie), za pomocą metody _InitializeRecord(), z wypełnieniem globalnym równym 0, gdy klasa lub tablica dynamiczna jest podana.

To samo dotyczy typów record i object we wszystkich wersjach Delphi.

Można zauważyć, że istnieje bug in modern version of Delphi (w tym przynajmniej Delphi 2009 i 2010), które czasem nie tworzą kodu do inicjowania obiektów na stosie. Będziesz musiał użyć rekordu, ale złamie kompatybilność z poprzednią wersją Delphi. :(

Oto struktura używana do przechowywania tych danych 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; 

Używając tych danych, tutaj jest na przykład to, co można zrobić:

Na przykład, oto jak dwa rekordy tego samego typu mogą być porównywane, za pomocą tego 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; 

Więc dla celów, jakie Delphi 7 RTTI mógł poinformować, w czasie wykonywania, jest pozycja każdego ciągu w rekordzie. Stosując powyższy kod, można łatwo utworzyć funkcję za pomocą indeksu polu:

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

Ale po prostu nie mamy potrzebnych informacji do wykonania żądania:

  • Nazwy pól nie są przechowywane w obrębie RTTI (tylko nazwa globalnego typu rekordu, a nawet nie zawsze AFAIK);
  • Tylko pola z policzeniem referencyjnym mają przesunięcie, a nie inne pola typu prostego (jak liczba całkowita/podwójna ...).

Jeśli naprawdę potrzebujesz tej funkcji, jedynym rozwiązaniem w Delphi 7 jest używanie nie rekordów, ale klas.

W Delphi 7, jeśli utworzysz klasę z opublikowanymi polami, będziesz mieć wszystkie potrzebne informacje dla wszystkich opublikowanych pól. Następnie możesz zaktualizować tak opublikowaną treść pola. Oto, co robi środowisko wykonawcze VCL, gdy odserializujesz zawartość .dfm w instancjach klasy lub ORM approach.

Powiązane problemy