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:
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:
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;
Delphi-Quellcode:
Habt ihr eine Idee, wie man das Dilemma umgehen kann?
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; |
AW: Generisches ToString für Enumerations
|
AW: Generisches ToString für Enumerations
Jupp, entweder Ord oder ein Integer-Typ, welcher die gleiche Größe hat. :wink:
SizeOf(Integer) <> SizeOf(TTestEnumSet) Versuch mal Byte, Word, LongWord/LongInt oder UInt64/Int64. |
AW: Generisches ToString für Enumerations
Selbst mit Ord() scheitert das ganze:
Delphi-Quellcode:
PS: Beachtet das mit dem set mal noch nicht.
[DCC Fehler] Project1.dpr(30): E2008 Inkompatible Typen
|
AW: Generisches ToString für Enumerations
Jetzt bleibt noch die Frage, wo Delphi gerade den Fehler meldet (beim 1. oder 2. Parameter)?
Bernhard |
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; |
AW: Generisches ToString für Enumerations
Ha, ich habs :stupid:
Delphi-Quellcode:
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.
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; @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:
|
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.
|
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:
Wenn noch wer was dran auszusetzen hat, dann nur her damit ;)
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; |
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.
|
AW: Generisches ToString für Enumerations
Zitat:
Zitat:
|
AW: Generisches ToString für Enumerations
Zitat:
Delphi unterstützt nämlich keine Typenparameter in einfachen Routinen. Zitat:
Dennoch kleine Korrektur:
Delphi-Quellcode:
denn AsString versucht, den Typen in einen string umzuwandeln, das wird fehlschlagen.
TValue.From<T>(Value).ToString
|
AW: Generisches ToString für Enumerations
Zitat:
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. |
AW: Generisches ToString für Enumerations
Zitat:
|
AW: Generisches ToString für Enumerations
Zitat:
Zitat:
Zitat:
Delphi-Quellcode:
Endet in einer Exception:
Result := TValue.From<String>(Value).AsType<T>;
Code:
Hatte jetzt aber keine Lust mehr da lange zu testen :stupid:
Im Projekt Project1.exe ist eine Exception der Klasse EInvalidCast mit der Meldung 'Ungültige Typumwandlung' aufgetreten.
Zitat:
|
AW: Generisches ToString für Enumerations
Zitat:
Zitat:
|
AW: Generisches ToString für Enumerations
Zitat:
|
AW: Generisches ToString für Enumerations
Zitat:
Delphi-Quellcode:
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?
TValue = record
public { ... } class function From<T>(const Value: T): TValue; static; { ... } end; 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. |
AW: Generisches ToString für Enumerations
Zitat:
Ü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. |
AW: Generisches ToString für Enumerations
Liste der Anhänge anzeigen (Anzahl: 1)
Delphi-Quellcode:
So, habe das ganze nun überarbeitet und um die Methoden
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;
Delphi-Quellcode:
und
SetToStr<T>()
Delphi-Quellcode:
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 :)
StrToSet<T>()
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:
. (ist quasi die Umkehrfunktion von SetToStr<T> -- wer hätte es gedacht ;) )
'[taOne, taTwo, taThree]'
-- 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