{Gelöst] Generic an eine Variable zuweisen

Für Fragen zur Programmiersprache auf welcher Lazarus aufbaut
Antworten
Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

{Gelöst] Generic an eine Variable zuweisen

Beitrag von Nimral »

Ich möchte eine "veraltete" Struktur durch eine "moderne" ersetzen. Es geht dabei um ein kleines CRUD Framework, das die üblichen 4 Methoden zur Manipulation von Dateien bereitstellt. Bisher arbeitet die Klasse übrigens ausgezeichnet mit Pointern, aber das ist, sagt man, nicht mehr zeitgemäß. Gut. Mit abgeleiteten Objekten ginge das ganz gut, aber mich stört, dass ich alle Funktionen, denen ein konkreter Datentyp übergeben werden muss (Create, Read) ... überschreiben oder überladen muss, obwohl in jeder immer der gleiche Code steht, der dann auf sowas hinausläuft:

Code: Alles auswählen

function TStringFile.Create(S:ShortString):Integer;

begin
... FileStream.WriteBuffer(S,SizeOf(S));  
end;

function TIntegerFile.Create(I:Integer):Integer;

begin
... FileStream.WriteBuffer(I,SizeOf(I));  
end;
. Da dachte ich mir, da wäre doch sicher mal ein Generic am richtigen Platz, wenn sich die Entwickler von FPC schon die Mühe gemacht haben um Generics zu implementieren.

Ich habe also mal folgendes kleine Testprojekt begonnen:

Code: Alles auswählen

  generic FileAccessor<T> = class
  private
    FCurrentRecord: Integer;
    FFileName: string;
    procedure SetCurrentRecord(AValue: Integer);
    procedure SetFileName(AValue: string);
  public
    function Create(Value: T): integer; overload;   // append to file
    function Create(): integer; overload;                // append empty record to file
    function Read(var Value: T): boolean;              // read from file
    function Write(Value: T; Idx: integer): boolean; // write to file
    function Delete(Idx: integer): boolean;             // delete from file (flag as overwritable)
    function toStr(Idx:Integer):String;                      // return content as hex dump for debugging
    property FileName: string read FFileName write SetFileName;  // Name of the data file
    property CurrentRecord : Integer read FCurrentRecord write SetCurrentRecord;     // pointer to the record last accessed
    function Count:Integer;  // return number of records in file
    constructor Create(AFileName:String);            //     
  end;

...

procedure FileAccessor.SetCurrentRecord(AValue: Integer);

begin
  if FCurrentRecord=AValue then Exit;
  if AValue > Self.Count - 1 then exit;
  if AValue < -1 then exit;
  FCurrentRecord:=AValue;
end;  

...

function FileAccessor.Create(Value: T): integer;

// Append a record, return the record index

var
  FileStream: TFileStream;

begin
  if FileExists(FileName) then
     FileStream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite)
  else
     FileStream := TFileStream.Create(FileName, fmCreate or fmShareDenyWrite);
  FileStream.Seek(0,soEnd);
  FileStream.WriteBuffer(Value,SizeOf(T));
  FreeAndNil(FileStream);
  result := Self.Count - 1;
end;
   
<T> kann alles Mögliche sein, idR ein packed record. Im Testbeispiel habe ich halt mal eine Integer und einen (Short)String verwendet:

Code: Alles auswählen

type
  TIntegerFile = specialize FileAccessor<Integer>;
  TStringFile = specialize FileAccessor<ShortString>;
var
  Form1: TForm1;
  IntegerFileAccessor: TIntegerFile;
  StringFileAccessor: TStringFile;
  CurrentAccessor: .... was nu?
Man sieht schon wo das hinläuft, so möchte ich dann durch die Files navigieren:

Code: Alles auswählen

procedure TForm1.ButtonNextClick(Sender: TObject);
begin
  if Assigned(CurrentAccessor) then
  begin
    CurrentAccessor.CurrentRecord := CurrentAccessor.CurrentRecord + 1;
    StatusBar1.SimpleText := Format('%s: %d', [CurrentAccessor.FileName, CurrentAccessor.CurrentRecord]);
  end;
end;
Mir fehlt jetzt noch das "Missing Link": kann ich CurrentAccessor irgendwie definieren oder Typecasten, so dass einmal IntegerFileAccessor und einmal StringFileAccessor benützt werden? Oder sind Generics für solche Aufgaben gar nicht geeignet? Ich habe da so meine Zweifel. Soweit ich sie verstanden habe (Generics sind nur eine Art Vorlage zum automatischen Generieren von statischem Code ...) kann man einiges mit ihnen machen, aber sie niemals so wie ich das gerne möchte an eine Variable zuweisen.

Richtig oder falsch?

HG, Armin.
Zuletzt geändert von Nimral am Fr 2. Jul 2021, 20:05, insgesamt 1-mal geändert.

PascalDragon
Beiträge: 825
Registriert: Mi 3. Jun 2020, 07:18
OS, Lazarus, FPC: L 2.0.8, FPC Trunk, OS Win/Linux
CPU-Target: Aarch64 bis Z80 ;)
Wohnort: München

Re: Generic an eine Variable zuweisen

Beitrag von PascalDragon »

Du könntest die Teile, die nichts direkt mit dem Typparameter zu tun haben in eine Elternklasse auslagern (z.B. TBaseFileAccessor), von der dann der generische TFileAccessor<> erbt. Du kannst dann dein CurrentAccessor also TBaseFileAccessor deklarieren. Hat auch den Vorteil, dass weniger Code dupliziert wird, da für jede unterschiedliche Spezialisierung in einer Unit der Code erneut erzeugt wird.
FPC Compiler Entwickler

Nimral
Beiträge: 390
Registriert: Mi 10. Jun 2015, 11:33

Re: Generic an eine Variable zuweisen

Beitrag von Nimral »

Danke für den Tipp, ich habs so gemacht wie vorgeschlagen, und es funktioniert prima!

HG, Armin.

Antworten