Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Generisches ToString für Enumerations (https://www.delphipraxis.net/160167-generisches-tostring-fuer-enumerations.html)

s.h.a.r.k 30. Apr 2011 14:25

Delphi-Version: XE

Generisches ToString für Enumerations
 
Hallo zusammen,

habe gerade mal wieder ein Problem bzgl. meiner Log-Klasse. Ich hätte gerne folgendes:
Delphi-Quellcode:
type
  TTestEnum = (teOne, teTwo, teThree {...});
  TTestEnumSet = set of TTestEnum;

var
  a : TTestEnum;
  b : TTestEnumSet;
begin
  a := teOne;
  TLog.Add(a); // -> liefert dann den String "teOne"

  // DAS HIER MAL NICHT BEACHTEN!!!
  //b := [teTwo, teThree];
  //TLog.Add(b); // -> Liefert den String "[teTwo, teThree]"
end;
Das Problem hierbei ist ja, dass die Add-Methode keine Ahnung von TTestEnum hat, d.h. ich sollte diese generisch gestalten, was an sich ja kein Problem wäre. Jetzt kommt allerdings das ABER: bei Generics gibts ja keine Einschränkung, dass ich eben nur Enumeration-Typen übergeben kann und somit funktioniert folgendes ja nicht:
Delphi-Quellcode:
uses
  TypInfo;

procedure TLog.Add<T>(Value: T);
var
  s : string;
begin
  s := GetEnumName(TypeInfo(T), Integer(Value)); // <- [DCC Fehler] Project1.dpr(27): E2089 Ungültige Typumwandlung
  { ... }
end;
Habt ihr eine Idee, wie man das Dilemma umgehen kann?

rollstuhlfahrer 30. Apr 2011 14:37

AW: Generisches ToString für Enumerations
 
muss da nicht ein
Delphi-Quellcode:
Ord(Value)
hin?? (vgl. OH)

Bernhard

himitsu 30. Apr 2011 14:43

AW: Generisches ToString für Enumerations
 
Jupp, entweder Delphi-Referenz durchsuchenOrd oder ein Integer-Typ, welcher die gleiche Größe hat. :wink:

SizeOf(Integer) <> SizeOf(TTestEnumSet)

Versuch mal Byte, Word, LongWord/LongInt oder UInt64/Int64.

s.h.a.r.k 30. Apr 2011 14:50

AW: Generisches ToString für Enumerations
 
Selbst mit Ord() scheitert das ganze:
Delphi-Quellcode:
[DCC Fehler] Project1.dpr(30): E2008 Inkompatible Typen
PS: Beachtet das mit dem set mal noch nicht.

rollstuhlfahrer 30. Apr 2011 15:05

AW: Generisches ToString für Enumerations
 
Jetzt bleibt noch die Frage, wo Delphi gerade den Fehler meldet (beim 1. oder 2. Parameter)?

Bernhard

daywalker9 30. Apr 2011 15:32

AW: Generisches ToString für Enumerations
 
Ich habs so gelöst:

Delphi-Quellcode:
class function TEnumGen<T>.GetName(AVal: T): string;
var
  bVal : Byte;
begin
  move(AVal,bVal,SizeOf(T));
  Result:=GetEnumName(TypeInfo(T),bVal);
end;

s.h.a.r.k 30. Apr 2011 15:34

AW: Generisches ToString für Enumerations
 
Ha, ich habs :stupid:

Delphi-Quellcode:
TLog = record
  class procedure AddEnum<T>(V: T); static;
end;

class procedure TLog.AddEnum<T>(V: T);
begin
  Writeln(GetEnumName(TypeInfo(T), PByte(@V)^));
end;
Der Tipp von himitsu war gut. Jetzt muss ich nur noch testen, ob das auch immer passen sollte. In meinen Fall passts jedenfalls. Aber man kann ja auch gewisse Werte für die Enum-Elemente definieren. Melde mich gleich wieder.

@daywalker: klar, so gehts auch :)

-- EDIT: So, habe mich jetzt nochmals ein wenig mit diesen Enumerations befasst und bin auf ein weiteres Problem gestoßen, welches dieses Zitat auf den Punkt bringt:
Zitat:

Zitat von http://delphi.wikia.com/wiki/GetEnumValue_Routine
Be aware that Typeinfo will be lost when the enumeration is given different ordinal values. With the next enumeration example it's not possible to use GetEnumValue and will result in 'E2134 Type 'TTestType' has no type info'

Schade, aber man kann daran wohl nix ändern.

Stevie 30. Apr 2011 16:14

AW: Generisches ToString für Enumerations
 
In der Logging Bibliothek, die wir in der Firma nutzen (recht bekanntes kommerzielles Produkt) ist das so gelöst, dass man bei LogEnum den Ordinalwert und den TypInfo übergibt. Da ist allerdings auch nix mit Generics.

s.h.a.r.k 30. Apr 2011 16:17

AW: Generisches ToString für Enumerations
 
Ich unterstütze alles ab Delphi 2010, da ich teilweise rege Verwendung der neuen RTTI mache. Nachdem es auch die netten Generics gibt, will ich dem Nutzer der Log-Komponente (Hauptnutzer bin wohl ich :mrgreen:) so viel wie möglich abnehmen. Daher mein Ansatz, aber danke für den Hinweis.

-- Edit: Hier noch schnell der Record mit den entsprechenden Methoden. Die Methode StrToEnum() gibt es in der TypInfo-Unit auch und heißt dort GetEnumValue(). Allerdings liefert GetEnumValue() einen Integer und müsste somit nochmals zusätzlich gecastet werden, was hier nicht der Fall ist -> Typsicherheit!
Delphi-Quellcode:
TEnumHelper = record
  class function EnumToStr<T>(Value: T): String; static;
  class function StrToEnum<T>(Value: String): T; static;
end;

class function TEnumHelper.EnumToStr<T>(Value: T): String;
var
  ti : PTypeInfo;
begin
  ti := TypeInfo(T);
  if (ti = nil) then
    raise Exception.Create('Type has not type information.');
  if (ti.Kind <> tkEnumeration) then
    raise Exception.Create('Type is not an enumeration.');
  Result := GetEnumName(ti, PByte(@Value)^);
end;

class function TEnumHelper.StrToEnum<T>(Value: String): T;
var
  ti : PTypeInfo;
  i : Byte;
  pt : ^T;
begin
  ti := TypeInfo(T);
  if (ti = nil) then
    raise Exception.Create('Type has not type information.');
  if (ti.Kind <> tkEnumeration) then
    raise Exception.Create('Type is not an enumeration.');

  i := GetEnumValue(ti, Value);
  pt := @i;
  Result := pt^;
end;
Wenn noch wer was dran auszusetzen hat, dann nur her damit ;)

Stevie 30. Apr 2011 16:50

AW: Generisches ToString für Enumerations
 
Du solltest die Größe deines Enum Typens überprüfen, der muss nämlich nicht immer 1 Byte sein.

Elvis 30. Apr 2011 20:02

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von s.h.a.r.k (Beitrag 1097915)
Wenn noch wer was dran auszusetzen hat, dann nur her damit ;)

Klaro...
Zitat:

Zitat von s.h.a.r.k (Beitrag 1097915)
Delphi-Quellcode:
TEnumHelper = record
  class function EnumToStr<T>(Value: T): String; static;
  class function StrToEnum<T>(Value: String): T; static;
end;

  1. Warum ein Record? Ernsthaft, wenn jmd irgendwo etwas sieht, was keine Klasse ist, wird er sich denken, dass es einen sehr guten Grund gab. Aber den gibt es hier überhaupt gar nicht.
    Solche ÖHM.... :gruebel: Momente haben in APIs nix zu suchen.
  2. Wenn du schon D2010 nutzt, dann nutze es auch. :P
    Ich habe es nicht ausprobiert und spreche den neuen Krams von Delphi nicht fließend, aber wenn ich eine generische Methode zum Wandeln in einen String ab D2010 schreiben müsste, würde das wohl so aussehen:
    Delphi-Quellcode:
    TValue.From<T>(deinWert).AsString();

Stevie 30. Apr 2011 20:48

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Elvis (Beitrag 1097961)
Warum ein Record? Ernsthaft, wenn jmd irgendwo etwas sieht, was keine Klasse ist, wird er sich denken, dass es einen sehr guten Grund gab. Aber den gibt es hier überhaupt gar nicht.
Solche ÖHM.... :gruebel: Momente haben in APIs nix zu suchen.

Wo ist der Unterschied einer statischen Klasse oder einem Rekord in diesem Fall?
Delphi unterstützt nämlich keine Typenparameter in einfachen Routinen.

Zitat:

Zitat von Elvis (Beitrag 1097961)
Wenn du schon D2010 nutzt, dann nutze es auch. :P
Ich habe es nicht ausprobiert und spreche den neuen Krams von Delphi nicht fließend, aber wenn ich eine generische Methode zum Wandeln in einen String ab D2010 schreiben müsste, würde das wohl so aussehen:

Hier stimme ich dir vollstens zu, habe ich auch vorhin nicht mehr dran gedacht, sonst hätt ich es erwähnt ;)
Dennoch kleine Korrektur:
Delphi-Quellcode:
TValue.From<T>(Value).ToString
denn AsString versucht, den Typen in einen string umzuwandeln, das wird fehlschlagen.

Elvis 30. Apr 2011 22:33

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Stevie (Beitrag 1097966)
Zitat:

Zitat von Elvis (Beitrag 1097961)
Warum ein Record? Ernsthaft, wenn jmd irgendwo etwas sieht, was keine Klasse ist, wird er sich denken, dass es einen sehr guten Grund gab. Aber den gibt es hier überhaupt gar nicht.
Solche ÖHM.... :gruebel: Momente haben in APIs nix zu suchen.

Wo ist der Unterschied einer statischen Klasse oder einem Rekord in diesem Fall?
Delphi unterstützt nämlich keine Typenparameter in einfachen Routinen.

Records sind eigentlich dafür da sich eigene Value Typres zu basteln, die auch ohne Garbage Collector wie normale Werte zu benutzen sind.
Klassen sind dafür da um zum Beispiel diese Art von Code zu enthalten. (Vor allem, da es auch noch komplett statisch ist... o_O )
Es gibt natürlich immer sinnvolle Ausnahmen, aber genau das ist hier überhaupt nicht der Fall gewesen.
Ich verbrachte nämlich beim lesen seines Beitrags etwas Zeit um rauszufinden, warum er es als Record deklarieren musste. Bis ich feststellte, dass es gar keinen Grund dafür gibt. Er hatte es einfach so gemacht, sozusagen eine Münze geworfen oder eine erhoffte Optimierung nach Cargo-Cult Style.
Du gibst ja auch keiner Klasse einen I-Präfix, außer wenn es unbedingt, aus irgendeinem drastischen Grund, sein muss.
Wenn du also eine Klasse mit einem I-Präfix siehst, wirst du wohl auch Zeit verbringen herauszufinden, warum der Autor das tun musste. Das gleiche gilt auch hier, nur 2 Nummern weniger dramatisch.

Stevie 30. Apr 2011 23:14

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Elvis (Beitrag 1097969)
Er hatte es einfach so gemacht, sozusagen eine Münze geworfen oder eine erhoffte Optimierung nach Cargo-Cult Style.
Du gibst ja auch keiner Klasse einen I-Präfix, außer wenn es unbedingt, aus irgendeinem drastischen Grund, sein muss.
Wenn du also eine Klasse mit einem I-Präfix siehst, wirst du wohl auch Zeit verbringen herauszufinden, warum der Autor das tun musste. Das gleiche gilt auch hier, nur 2 Nummern weniger dramatisch.

Ich kann nicht hellsehen, aber da ich einige der jüngsten Neuentwicklungen seit Delphi 2009 kenne, würde ich vermuten, dass er sich das von den Jungs bei Embarcadero abgeschaut hat. Die pflegen das nämlich auch (z.B. IOUtils.pas). Soll nicht heißen, dass ich das sonderlich gut finde. Ich finde es im Hinblick auf Testbarkeit (Stichwort: Mocks) eher ziemlich schäbig. Leider tun dem class methods aber auch keinen Abbruch, weil du immer noch ne statische Abhängigkeit hast.

s.h.a.r.k 1. Mai 2011 02:49

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Stevie (Beitrag 1097922)
Du solltest die Größe deines Enum Typens überprüfen, der muss nämlich nicht immer 1 Byte sein.

Da hat der Herr wohl vollkommen recht, baue ich morgen ein :stupid: Aber im großen und ganzen müsste das der Compiler sogar abfangen, da man ja einen Enum angeben muss, der im Moment ja immer 1 Byte groß ist. Bei den Enums, bei denen man Ordinalwerte angeben kann, wird ja eine Exception geworfen, da gar keine Typeinformationen vorhanden sind.

Zitat:

Zitat von Elvis (Beitrag 1097961)
  1. Warum ein Record? Ernsthaft, wenn jmd irgendwo etwas sieht, was keine Klasse ist, wird er sich denken, dass es einen sehr guten Grund gab. Aber den gibt es hier überhaupt gar nicht.
    Solche ÖHM.... :gruebel: Momente haben in APIs nix zu suchen.

Ich habe es mir wahrlich im Source der VCL abgeschaut und versteh auch nicht den Sinn, warum ich das nicht machen sollte. Selbst TValue ist ein solcher Record, ebenso viele Dinge aus der neuen RTTI. Mir ist schon klar, wofür Records eigentlich gedacht sind, aber wo genau ist das Problem, dass ich Records verwende? Das kann dem Nutzer des Codes doch gänzlich egal sein, oder? Er ruft die Methode exakt gleich auf, bis auf dass anstatt record einfach class steht, was er eigentlich nicht sieht. Ich habe jetzt nicht das schlagende Argument gesehen, warum das denn unbedingt eine Klasse sein muss. Klar, man kann jetzt darüber streiten, was man mit Records alles machen können sollte und was nicht, aber ich sehe hier weniger ein Problem. Aber wie so oft, ich lasse mich gerne belehren :)
Zitat:

Zitat von Elvis (Beitrag 1097961)
  1. Wenn du schon D2010 nutzt, dann nutze es auch. :P
    Ich habe es nicht ausprobiert und spreche den neuen Krams von Delphi nicht fließend, aber wenn ich eine generische Methode zum Wandeln in einen String ab D2010 schreiben müsste, würde das wohl so aussehen:
    Delphi-Quellcode:
    TValue.From<T>(deinWert).AsString();

Okay, überzeugt. Habs gerade geändert. Allerdings geht das hier nicht:
Delphi-Quellcode:
Result := TValue.From<String>(Value).AsType<T>;
Endet in einer Exception:
Code:
Im Projekt Project1.exe ist eine Exception der Klasse EInvalidCast mit der Meldung 'Ungültige Typumwandlung' aufgetreten.
Hatte jetzt aber keine Lust mehr da lange zu testen :stupid:
Zitat:

Zitat von Elvis (Beitrag 1097969)
[...] oder eine erhoffte Optimierung nach Cargo-Cult Style. [...]

WTF? Was für ein Style? :mrgreen:

Elvis 1. Mai 2011 09:37

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von s.h.a.r.k (Beitrag 1097982)
Okay, überzeugt. Habs gerade geändert. Allerdings geht das hier nicht:
Delphi-Quellcode:
Result := TValue.From<String>(Value).AsType<T>;
Endet in einer Exception:
Code:
Im Projekt Project1.exe ist eine Exception der Klasse EInvalidCast mit der Meldung 'Ungültige Typumwandlung' aufgetreten.
Hatte jetzt aber keine Lust mehr da lange zu testen :stupid:

Hmm, hatte mir nur TValue in den API Docs auf Embacadings.com angesehen. Hatte einfach erwartet dass es in beide Richtungen geht. Embarcadings scheint wohl auf Öhm-Momente in ihren APIs viel Wert zu legen, oder einfach nur keinen Anspruch zu haben... :freak:

Zitat:

Zitat:

Zitat von Elvis (Beitrag 1097969)
[...] oder eine erhoffte Optimierung nach Cargo-Cult Style. [...]

WTF? Was für ein Style? :mrgreen:
http://en.wikipedia.org/wiki/Cargo_c...mming#Overview

Stevie 1. Mai 2011 10:52

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Elvis (Beitrag 1098004)
Zitat:

Zitat von s.h.a.r.k (Beitrag 1097982)
Okay, überzeugt. Habs gerade geändert. Allerdings geht das hier nicht:
Delphi-Quellcode:
Result := TValue.From<String>(Value).AsType<T>;
Endet in einer Exception:
Code:
Im Projekt Project1.exe ist eine Exception der Klasse EInvalidCast mit der Meldung 'Ungültige Typumwandlung' aufgetreten.
Hatte jetzt aber keine Lust mehr da lange zu testen :stupid:

Hmm, hatte mir nur TValue in den API Docs auf Embacadings.com angesehen. Hatte einfach erwartet dass es in beide Richtungen geht. Embarcadings scheint wohl auf Öhm-Momente in ihren APIs viel Wert zu legen, oder einfach nur keinen Anspruch zu haben... :freak:

Wie ich bereits sagte. In die eine Richtung geht es nur wegen dem ToString. Das wandelt alles in seine String Repräsentation um (genau wie das ToString in TObject). Es findet hier keine Typenkonvertierung statt. Bei deinem Versuch einen String in den Typen umzuwandeln bemüht die RTTI intern die Typenkonvertierung, sofern es sich um sehr ähnliche Typen handelt (Float typ, ordinal typ, etc). Und TValue ist nicht für eine Typenkonvertierung darüber hinaus gedacht und funktioniert auch nicht, wie du gesehen hast. Du kannst damit nichtmal nen string in nen integer umwandeln, obwohl das eigentlich technisch kein Probelm wäre.

s.h.a.r.k 1. Mai 2011 13:33

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von Elvis (Beitrag 1098004)
Hmm, hatte mir nur TValue in den API Docs auf Embacadings.com angesehen. Hatte einfach erwartet dass es in beide Richtungen geht. Embarcadings scheint wohl auf Öhm-Momente in ihren APIs viel Wert zu legen, oder einfach nur keinen Anspruch zu haben... :freak:

Allein aufgrund der automatischen Garbage Collection bei Records kann ich verstehen, warum es so gemacht ist, wie es gemacht ist. Sonst müsstest du ja jedes mal ein TValue-Objekt erzeugen und freigeben, wenn du damit arbeiten willst. Records machen das halt sehr viel bequemer. Klar, in meinem Fall ändert sich nicht wirklich viel, aber ich hab mir mal den TValue-Record näher angeschaut und allein schon diese Zeile spricht für sich, wie ich finde:
Delphi-Quellcode:
TValue = record
public
  { ... }
  class function From<T>(const Value: T): TValue; static;
  { ... }
end;
Wäre TValue eine Klasse, dann müsste man die Speicherverwaltung teilweise intern, ebenso aber auch extern durch den Programmierer vornehmen, dass immer alles passt und keine Speicherleaks entstehen. Klar, man könnte den Record (bzw. dann evtl. die Klasse) anders gestalten, aber was genau spricht gegen eine solche Verwendung?

Ich bin echt gerne bereit, meine Ansichten zu ändern, wie schon gesagt, aber ich verstehe den Grund, warum ich das tun sollte, nicht wirklich?! Ich habe extra nochmals auf Wikipedia nachgelesen, wofür Records (bzw. Structs) eigentlich da sind, aber nachdem deren Funktionalität erweitert wurde, sehe ich weniger Probleme darin auch deren Vorteile zu verwenden.

Stevie 1. Mai 2011 14:10

AW: Generisches ToString für Enumerations
 
Zitat:

Zitat von s.h.a.r.k (Beitrag 1098056)
Zitat:

Zitat von Elvis (Beitrag 1098004)
Hmm, hatte mir nur TValue in den API Docs auf Embacadings.com angesehen. Hatte einfach erwartet dass es in beide Richtungen geht. Embarcadings scheint wohl auf Öhm-Momente in ihren APIs viel Wert zu legen, oder einfach nur keinen Anspruch zu haben... :freak:

Allein aufgrund der automatischen Garbage Collection bei Records kann ich verstehen, warum es so gemacht ist, wie es gemacht ist. Sonst müsstest du ja jedes mal ein TValue-Objekt erzeugen und freigeben, wenn du damit arbeiten willst. Records machen das halt sehr viel bequemer. Klar, in meinem Fall ändert sich nicht wirklich viel, aber ich hab mir mal den TValue-Record näher angeschaut und allein schon diese Zeile spricht für sich, wie ich finde:
Delphi-Quellcode:
TValue = record
public
  { ... }
  class function From<T>(const Value: T): TValue; static;
  { ... }
end;
Wäre TValue eine Klasse, dann müsste man die Speicherverwaltung teilweise intern, ebenso aber auch extern durch den Programmierer vornehmen, dass immer alles passt und keine Speicherleaks entstehen. Klar, man könnte den Record (bzw. dann evtl. die Klasse) anders gestalten, aber was genau spricht gegen eine solche Verwendung?

Ich bin echt gerne bereit, meine Ansichten zu ändern, wie schon gesagt, aber ich verstehe den Grund, warum ich das tun sollte, nicht wirklich?! Ich habe extra nochmals auf Wikipedia nachgelesen, wofür Records (bzw. Structs) eigentlich da sind, aber nachdem deren Funktionalität erweitert wurde, sehe ich weniger Probleme darin auch deren Vorteile zu verwenden.

Ihr redet aneinander vorbei. Bei TValue handelt es sich um einen Wertetypen. Bei deinem record nicht, denn er hat keinen State sondern nur 2 Methoden. Und dafür kann man auch ohne Probleme eine Klasse benutzen, weil man für die Benutzung von class methods nix instanzieren muss. Aber wie ich bereits sagte, spielt es für diesen konkreten Fall (Containertyp für parametriesierte Methoden) keine Rolle, ob du eine Klasse mit class methods nutzt oder einen record mit static methods.

Übrigens:
Du könntest auch einen record helper für TValue schreiben :)
Und schau dir mal die Implementierung von TRttiEnumerationType.GetValue in der Rtti.pas an.

s.h.a.r.k 3. Mai 2011 19:40

AW: Generisches ToString für Enumerations
 
Liste der Anhänge anzeigen (Anzahl: 1)
Delphi-Quellcode:
TApEnumerations = class
public
  class function EnumToStr<T>(const Value: T): String; static;
  class function StrToEnum<T>(const Value: String): T; static;

  class function SetToStr<T>(const Value: T): String; static;
  class function StrToSet<T>(const Value: String): T; static;
end;
So, habe das ganze nun überarbeitet und um die Methoden
Delphi-Quellcode:
SetToStr<T>()
und
Delphi-Quellcode:
StrToSet<T>()
erweitert. Zudem habe ich hier nicht mehr den Anspruch die neue RTTI zu nutzen, da diese ja nur die alte RTTI-Methoden kapselt und so etwas langsamer ist -- klar, man kann hier drüber streiten, aber es soll hier um die Funktionalität gehen :)

Weiterhin habe ich den record durch class ersetzt, da ich wohl das Argument verstanden habe, warum ich hier eigentlich class verwenden sollte.

In diesem Sinne, wettert drauf los :mrgreen:

PS: Ich sollte noch sagen, dass StrToSet<T>() als Typen den set-Typen erwartet (z.B. TTestEnumSet) und als Parameter eine komma-separierte Aufzählung, die in eckigen Klammer platziert ist -- Beispiel:
Delphi-Quellcode:
'[taOne, taTwo, taThree]'
. (ist quasi die Umkehrfunktion von SetToStr<T> -- wer hätte es gedacht ;) )

-- Edit: Habe das ganze nun mal in eine Unit gepackt und in den Anhang gesteckt. Dadurch wird der Beitrag dezent übersichtlicher.


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:09 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz