Results 1 to 9 of 9

Thread: Procedure met een ongedefinieerd type als parameter

  1. #1

    Procedure met een ongedefinieerd type als parameter

    Ik heb nu een aantal functies op een van mijn classes waar ik verschillende typen kan wegschrijven naar een Stream.

    Delphi Code:
    1. procedure FileWriteString(AStream: TStream; const Value: string);
    2. procedure FileWriteInteger(AStream: TStream; const Value: Integer);
    3. procedure FileWriteBoolean(AStream: TStream; const Value: Boolean);
    4. procedure FileWriteExtended(AStream: TStream; const Value: Extended);
    5. procedure FileWriteSingle(AStream: TStream; const Value: Single);
    6. procedure FileWriteColor(AStream: TStream; const Value: TColor);
    7.  
    8. procedure FileWriteString(AStream: TStream; const Value: string);
    9. var
    10.   ASize   : Integer;
    11.   AString : AnsiString;
    12. begin
    13.   AString := AnsiString(Value);
    14.   ASize := Length(AString);
    15.   AStream.WriteBuffer(ASize, SizeOf(Integer));
    16.   if ASize > 0 then
    17.   AStream.WriteBuffer(AString[1], ASize);
    18. end;
    19.  
    20. procedure FileWriteInteger(AStream: TStream; const Value: Integer);
    21. begin
    22.   AStream.WriteBuffer(Value, SizeOf(Integer));
    23. end;
    24.  
    25. procedureFileWriteBoolean(AStream: TStream; const Value: Boolean);
    26. begin
    27.   AStream.WriteBuffer(Value, SizeOf(Boolean));
    28. end;
    29.  
    30. procedure FileWriteExtended(AStream: TStream; const Value: Extended);
    31. begin
    32.   AStream.WriteBuffer(Value, SizeOf(Extended));
    33. end;
    34.  
    35. procedure FileWriteSingle(AStream: TStream; const Value: Single);
    36. begin
    37.   AStream.WriteBuffer(Value, SizeOf(Single));
    38. end;
    39.  
    40. procedure FileWriteColor(AStream: TStream; const Value: TColor);
    41. begin
    42.   AStream.WriteBuffer(Value, SizeOf(TColor));
    43. end;

    Nu heb ik eigenlijk nog een heel tal aan typen die ik zou willen wegschrijven, en dacht dat dit wellicht beter/makkelijker kan dan voor elk type een writer procedure te maken. Ik weet dat bijvoorbeeld de Format functie een
    Delphi Code:
    1. array of const
    gebruikt, waarin je dus elk type kan doorgeven. Zou dit ook mogelijk zijn met bovenstaande? Dat ik dus een procedure heb met als parameter const:
    Delphi Code:
    1. procedure FileWrite(AStream: TStream; const Value);
    2. begin
    3.   AStream.WriteBuffer(Value, SizeOf(Value));
    4. end;

    Het enige waar ik hier tegen aanloop is dat bijvoorbeeld als ik hier een WideString in pass, deze niet goed wordt weggeschreven omdat ik dan dit zou moeten doen:
    Delphi Code:
    1. procedure FileWrite(AStream: TStream; const Value);
    2. begin
    3.   AStream.WriteBuffer(Value, Length(String(Value)) * SizeOf(Value) );
    4. end;
    Omdat als ik een SizeOf doe op de value, ik de grootte van de WideChar krijg (in dit geval dus 4, bij een Integer ook 4, bij een Double bijvoorbeeld 8).

    Is er een manier om er achter te komen welk type de value is? Zodat ik op basis daarvan de juiste lengte in bytes kan doorgeven?

  2. #2
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,703
    Je zou misschien iets met generics kunnen doen denk ik, al zul je voor diverse typen (zoals strings) afwijkende code moeten schrijven:
    Delphi Code:
    1. procedure TForm1.FileWrite<T>(const AStream: TStream; const AValue: T);
    2. var
    3.   Len: SmallInt;
    4. begin
    5.   if TypeInfo(T) = TypeInfo(string) then // Mocht je op meer typen moeten controlen, dan is een "case GetTypeKind(T) of" misschien handiger
    6.   begin
    7.     Len := Length(PString(@AValue)^);
    8.     if AStream.Write(Len, SizeOf(SmallInt)) = SizeOf(SmallInt) then
    9.       AStream.WriteBuffer(PString(@AValue)^[1], Len * SizeOf(Char));
    10.   end
    11.   else
    12.     AStream.WriteBuffer(AValue, SizeOf(T));
    13. end;
    14.  
    15. procedure TForm1.Button1Click(Sender: TObject);
    16. var
    17.   FS: TFileStream;
    18. begin
    19.   FS := TFileStream.Create('C:\Temp\Test.dat', fmCreate);
    20.   try
    21.     FileWrite(FS, 8);
    22.     FileWrite(FS, 24);
    23.     FileWrite(FS, 66);
    24.     FileWrite(FS, clRed);
    25.     FileWrite(FS, Now);
    26.     FileWrite(FS, 'Is dit waar?');
    27.     FileWrite(FS, True);
    28.   finally
    29.     FS.Free;
    30.   end;
    31. end;

    Overigens is mijn ervaring dat generics wel leuk kunnen zijn, maar niet zaligmakend, dus soms is het beter om iets meer te investeren in (ogenschijnlijk) meer en duidelijkere code, maar soms kunnen ze een uitkomst bieden bij het voorkomen van repeterende code.
    Last edited by VideoRipper; 28-Sep-21 at 19:04.
    TMemoryLeak.Create(Nil);

  3. #3
    Oh wow, dat is heel gaaf! Dat is inderdaad precies wat ik zocht! Dankjewel ik ga eens kijken of ik hiermee verder kan

  4. #4
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,703
    Vergeet niet dat in mijn voorbeeld boven, alleen de UnicodeString/WideString wordt afgehandeld; mocht je ook AnsiStrings gebruiken, dan moet je ook daar een uitzondering voor toevoegen in de code.
    Hetzelfde geldt wellicht voor typen die achter de schermen eigenlijk een ander type zijn, zoals bijvoorbeeld TDateTime (dat eigenlijk een float is), want je kunt aan het resultaat niet zien van welk type de data eigenlijk is.
    Je vroeg alleen hoe je verschillende waarden "Plat" kunt opslaan, dus ik ga ervan uit dat je een reader hebt die weet wat hij waar moet verwachten bij het weer uitlezen van de data.

    Om alvast je volgende vraag voor te zijn: Delphi kan (of in ieder kon tot op heden) niet overweg met generics in regular methods (functies/procedures die geen onderdeel uitmaken van een klasse).
    Dit valt op te lossen door ze dan in een dummy-klasse te stoppen en aan te roepen als class method.
    Delphi Code:
    1. type
    2.   TDummyWriter = class
    3.   public
    4.     class procedure FileWrite<T>(const AStream: TStream; const AValue: T);
    5.   end;

    Een andere oplossing zou een class helper voor TStream kunnen zijn:
    Delphi Code:
    1. type
    2.   TStreamHelper = class helper for TStream
    3.   public
    4.     procedure WriteValue<T>(const AValue: T);
    5.   end;
    6.  
    7. { TStreamHelper }
    8.  
    9. procedure TStreamHelper.WriteValue<T>(const AValue: T);
    10. var
    11.   Len: SmallInt;
    12. begin
    13.   if TypeInfo(T) = TypeInfo(string) then
    14.   begin
    15.     Len := Length(PString(@AValue)^);
    16.     if Write(Len, SizeOf(SmallInt)) = SizeOf(SmallInt) then
    17.       WriteBuffer(PString(@AValue)^[1], Len * SizeOf(Char));
    18.   end
    19.   else
    20.     WriteBuffer(AValue, SizeOf(T));
    21. end;
    22.  
    23. procedure TForm1.Button1Click(Sender: TObject);
    24. var
    25.   FS: TFileStream;
    26. begin
    27.   FS := TFileStream.Create('C:\Temp\Test.dat', fmCreate);
    28.   try
    29.     FS.WriteValue(8);
    30.     FS.WriteValue(24);
    31.     FS.WriteValue(66);
    32.     FS.WriteValue(clRed);
    33.     FS.WriteValue(Now);
    34.     FS.WriteValue('Is dit waar?');
    35.     FS.WriteValue(True);
    36.   finally
    37.     FS.Free;
    38.   end;
    39. end;
    Onthoudt dat dit ook weer nadelen heeft, zo kun je maar één helper per class hebben.
    Mocht je altijd en alleen gebruik maken van een bepaalde TStream afgeleide (bijvoorbeeld TFileStream), dan kun je dit laatste probleem natuurlijk oplossen door gewoon een afgeleide te maken van dat type waar aan je de "WriteValue" method toevoegt:
    Delphi Code:
    1. type
    2.   TSpecialFileStream = class(TFileStream)
    3.   public
    4.     procedure WriteValue<T>(const AValue: T);
    5.   end;
    Last edited by VideoRipper; 29-Sep-21 at 12:30.
    TMemoryLeak.Create(Nil);

  5. #5
    Met array of const kan je iets soortgelijks doen. Je krijgt dan eigenlijk een array van TVarRec mee, eigenlijk de achterliggende techniek van Variants.
    Dat zijn records met daarin een type en een stukje data (of een pointer naar de data).

    Op Stackoverflow staat een antwoord met een voorbeeld hoe je een array of const om kan zetten naar strings, met een conversie voor de verschillende typen.
    1+1=b

  6. #6
    Top dank jullie wel, ik heb eens gekeken naar de source van de TBinaryWriter en TBinaryReader, daar gebruiken ze een WriteValue en ReadValue procedure die voor elk type overload is. Ze hebben dus voor de basis typen (Integer, Boolean, String, Double, Float, etc) een eigen procedure. Ik heb nu 2 units gemaakt, 1 op de manier zoals het ook in de TBinarywriter gedaan is - dus voor elk type een overload procedure waardoor ik in code ook "gewoon 1 procedure kan gebruiken". En in de 2e unit heb ik het voorbeeld van @Videoripper gebruikt. Zo kan ik eens testen wat beter/sneller/duidelijker is.
    Wel heb ik gemerkt dat door de Generics de executable wel een pak groter wordt - maar of dat nog iets uitmaakt tegenwoordig (een exe van 15mb is niet zo vreemd meer).

  7. #7
    Fornicatorus Formicidae VideoRipper's Avatar
    Join Date
    Mar 2005
    Location
    Vicus Saltus Orientalem
    Posts
    5,703
    Quote Originally Posted by Reidinga View Post
    Wel heb ik gemerkt dat door de Generics de executable wel een pak groter wordt
    Ja, dat is inderdaad zo'n ander dingetje.
    Sowieso zijn applicaties van latere Delphi versies niet te vergelijken met die van (bijvoorbeeld) Delphi 7: niet alleen in functionaliteit en mogelijkheden, maar ook de executable groottes verschillen enorm.

    Elk voordeel hep z'n nadeel zullen we maar zeggen.
    TMemoryLeak.Create(Nil);

  8. #8
    Ja zeker, ik weet soms gewoon niet zo goed in het kader van "Don't repeat yourself" door een 20 tal overload procedures te schrijven of dat nu een "betere" oplossing is dan een procedure te gebruiken die alle typen kan wegschrijven. Nu in Delphi/Pascal is juist die overload bedoeld voor dit soort situaties denk ik? Het voordeel van losse procedures vind ik persoonlijk dat het debuggen wel makkelijker mee is, nadeel is dat je weer een "heel pak" extra code moet schrijven.. Uiteindelijk is het soms wel mogelijk om te optimaliseren/inkorten van code maar de leesbaarheid wordt wel minder - zeker omdat ik in Delphi programmeer voor mijn eigen (prive/zakelijk) en op mijn dagelijks werk vooral werk met PHP/Python/Javascript/C# - en als ik dan een week of zelfs een paar dagen later terug in mijn code ga werken het een pak lastiger kan zijn om er weer in te komen. Ik comment wel veel van mijn code - niet overdadig maar genoeg om de volgende keer te kunnen zien waar ik was en waarom ik iets deed.

  9. #9
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    De EXE grootte veranderingen wijt ik echter eerder aan unicode en, nog meer, aan de zg extended RTTI van XE2 en later. Minder aan compiler/taal constructies.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •