2015-07-24 6 views
10

Ich versuche, Funktionen zu schreiben, die eine Enumeration in Zeichenfolge und wieder zurück konvertieren werden.Generische Funktionen zum Konvertieren einer Enumeration in String und zurück

dh:

TConversions = class 
    strict private 
    public 
     class function StringToEnumeration<T:class>(x:String):T; 
     class function EnumerationToString<T:class>(x:T):String; 
    end; 

im Implementationsabschnitt habe ich

uses 
System.TypInfo 
; 

class function TConversions.StringToEnumeration<T>(x:String):T; 
begin 
    Result := T(GetEnumValue(TypeInfo(T), x)); 
end; 

class function TConversions.EnumerationToString<T>(x:T):String; 
begin 
    Result := GetEnumName(TypeInfo(T), integer(x)); 
end; 

Das Problem ist, eine Enumeration ist nicht vom Typ T:class in pascal. Ich kann T:record entweder nicht verwenden.

Ist dies in Pascal möglich?

+0

http://www.thedelphigeek.com/2013/03/using-generics-to-manipulate-enumerable.html?m=1 –

+1

In der Unit Rtti gibt es 'TRttiEnumerationType.GetName (AValue: T): string;' und 'TRttiEnumerationType.GetValue (const AName: string): T;' –

+0

Neuere Version (nicht sicher seit wann) erlaubt 'record zu verwenden 'als Grenze (für alle Werttypen, einschließlich Enums):' TSomeEnumThingy ' –

Antwort

9

Sie müssen mit den Dingen ein bisschen herumspielen. Es gibt kein generisches Element für Enums, also umgehen wir es, indem wir mit Byte, Word und Cardinal in und aus dem Enum werfen.

program Project6; 

{$APPTYPE CONSOLE} 
{$R *.res} 

uses 
    System.SysUtils, System.TypInfo; 

type 
    TConversions<T> = record 
    class function StringToEnumeration(x: String): T; static; 
    class function EnumerationToString(x: T): String; static; 
    end; 

class function TConversions<T>.StringToEnumeration(x: String): T; 
begin 
    case Sizeof(T) of 
    1: PByte(@Result)^ := GetEnumValue(TypeInfo(T), x); 
    2: PWord(@Result)^ := GetEnumValue(TypeInfo(T), x); 
    4: PCardinal(@Result)^ := GetEnumValue(TypeInfo(T), x); 
    end; 
end; 

class function TConversions<T>.EnumerationToString(x: T): String; 
begin 
    case Sizeof(T) of 
    1: Result := GetEnumName(TypeInfo(T), PByte(@x)^); 
    2: Result := GetEnumName(TypeInfo(T), PWord(@x)^); 
    4: Result := GetEnumName(TypeInfo(T), PCardinal(@x)^); 
    end; 
end; 

type 
    TMyEnum = (me_One, me_Two, me_Three); 
    TMyEnum2 = (m1,m2,m3,m4,m5,m6,m7,m8,m9,m10,m11,m12,m13,m14,m15,m16,m17,m18,m19,m20, 
       m21,m22,m23,m24,m25,m26,m27,m28,m29,m30,m31,m32,m33,m34,m35,m36,m37,m38,m39,m40, 
       m41,m42,m43,m44,m45,m46,m47,m48,m49,m50,m51,m52,m53,m54,m55,m56,m57,m58,m59,m60, 
       ma1,ma2,ma3,ma4,ma5,ma6,ma7,ma8,ma9,ma10,ma11,ma12,ma13,ma14,ma15,ma16,ma17,ma18,ma19,ma20, 
       ma21,ma22,ma23,ma24,ma25,ma26,ma27,ma28,ma29,ma30,ma31,ma32,ma33,ma34,ma35,ma36,ma37,ma38,ma39, 
       ma40,ma41,ma42,ma43,ma44,ma45,ma46,ma47,ma48,ma49,ma50,ma51,ma52,ma53,ma54,ma55,ma56,ma57,ma58,ma59,ma60, 
       mb1,mb2,mb3,mb4,mb5,mb6,mb7,mb8,mb9,mb10,mb11,mb12,mb13,mb14,mb15,mb16,mb17,mb18,mb19, 
       mb20,mb21,mb22,mb23,mb24,mb25,mb26,mb27,mb28,mb29,mb30,mb31,mb32,mb33,mb34,mb35,mb36,mb37,mb38,mb39, 
       mb40,mb41,mb42,mb43,mb44,mb45,mb46,mb47,mb48,mb49,mb50,mb51,mb52,mb53,mb54,mb55,mb56,mb57,mb58,mb59,mb60, 
       mc1,mc2,mc3,mc4,mc5,mc6,mc7,mc8,mc9,mc10,mc11,mc12,mc13,mc14,mc15,mc16,mc17,mc18,mc19, 
       mc20,mc21,mc22,mc23,mc24,mc25,mc26,mc27,mc28,mc29,mc30,mc31,mc32,mc33,mc34,mc35,mc36,mc37,mc38,mc39, 
       mc40,mc41,mc42,mc43,mc44,mc45,mc46,mc47,mc48,mc49,mc50,mc51,mc52,mc53,mc54,mc55,mc56,mc57,mc58,mc59,mc60, 
       md1,md2,md3,md4,md5,md6,md7,md8,md9,md10,md11,md12,md13,md14,md15,md16,md17,md18,md19, 
       md20,md21,md22,md23,md24,md25,md26,md27,md28,md29,md30,md31,md32,md33,md34,md35,md36,md37,md38,md39, 
       md40,md41,md42,md43,md44,md45,md46,md47,md48,md49,md50,md51,md52,md53,md54,md55,md56,md57,md58,md59,md60, 
       me1,me2,me3,me4,me5,me6,me7,me8,me9,me10,me11,me12,me13,me14,me15,me16,me17,me18,me19, 
       me20,me21,me22,me23,me24,me25,me26,me27,me28,me29,me30,me31,me32,me33,me34,me35,me36,me37,me38,me39, 
       me40,me41,me42,me43,me44,me45,me46,me47,me48,me49,me50,me51,me52,me53,me54,me55,me56,me57,me58,me59,me60, 
       mf1,mf2,mf3,mf4,mf5,mf6,mf7,mf8,mf9,mf10,mf11,mf12,mf13,mf14,mf15,mf16,mf17,mf18,mf19, 
       mf20,mf21,mf22,mf23,mf24,mf25,mf26,mf27,mf28,mf29,mf30,mf31,mf32,mf33,mf34,mf35,mf36,mf37,mf38,mf39, 
       mf40,mf41,mf42,mf43,mf44,mf45,mf46,mf47,mf48,mf49,mf50,mf51,mf52,mf53,mf54,mf55,mf56,mf57,mf58,mf59,mf60); 

var 
    enum: TMyEnum; 
    enum2: TMyEnum2; 
begin 
    enum := me_Two; 
    WriteLn(TConversions<TMyEnum>.EnumerationToString(enum)); 
    enum := me_One; 
    WriteLn(TConversions<TMyEnum>.EnumerationToString(enum)); 
    enum := TConversions<TMyEnum>.StringToEnumeration('me_Three'); 
    WriteLn(TConversions<TMyEnum>.EnumerationToString(enum)); 
    enum2 := m17; 
    WriteLn(TConversions<TMyEnum2>.EnumerationToString(enum2)); 
    ReadLn; 
end. 
+1

Was passiert, wenn Sie z. 'TConversions .EnumerationToString (42)'? Sie haben keine Überprüfung, ob der Typ tatsächlich Enumeration ist und ich bin mir nicht ganz sicher, was innerhalb der 'GetEnumName'-Funktion passiert (habe keine Quelle von Hand). – TLama

+0

@TLama TConversions .EnumerationToString (42) gibt 42 zurück :) - Sie haben jedoch Recht. Ich werde einige Fehlerprüfung hinzufügen. – Graymatter

+0

@Graymatter Wie haben Sie die Fehlerprüfung implementiert? Ich bin sehr an der Lösung interessiert, die Sie geschrieben haben (ich habe sie tatsächlich gewählt), da ich auf Delphi XE3 bin, wo aus irgendeinem seltsamen Grund eine Klassenfunktion der TRttiEnumerationType-Klasse privat ist und ich nicht darauf zugreifen kann. In Delphi => XE5 sind sie in der öffentlichen Schnittstelle Abschnitt –

6

Es scheint keine T:enum generische Typ Einschränkung zu sein, so denke ich, das Beste, was Sie tun können, die Art zur Laufzeit ist zu überprüfen, so etwas wie diese:

bearbeiten: Basierend auf Davids Kommentar habe ich hinzugefügt die T: record Einschränkung, die verwendet werden kann, um Typen einzuschränken (und Klassentypen auszuschließen).

type 
    TConversions = class 
    public 
    class function StringToEnumeration<T: record>(const S: string): T; 
    class function EnumerationToString<T: record>(Value: T): string; 
    end; 

class function TConversions.EnumerationToString<T>(Value: T): string; 
var 
    P: PTypeInfo; 
begin 
    P := PTypeInfo(TypeInfo(T)); 
    case P^.Kind of 
    tkEnumeration: 
     case GetTypeData(P)^.OrdType of 
     otSByte, otUByte: 
      Result := GetEnumName(P, PByte(@Value)^); 
     otSWord, otUWord: 
      Result := GetEnumName(P, PWord(@Value)^); 
     otSLong, otULong: 
      Result := GetEnumName(P, PCardinal(@Value)^); 
     end; 
    else 
     raise EArgumentException.CreateFmt('Type %s is not enumeration', [P^.Name]); 
    end; 
end; 

class function TConversions.StringToEnumeration<T>(const S: string): T; 
var 
    P: PTypeInfo; 
begin 
    P := PTypeInfo(TypeInfo(T)); 
    case P^.Kind of 
    tkEnumeration: 
     case GetTypeData(P)^.OrdType of 
     otSByte, otUByte: 
      PByte(@Result)^ := GetEnumValue(P, S); 
     otSWord, otUWord: 
      PWord(@Result)^ := GetEnumValue(P, S); 
     otSLong, otULong: 
      PCardinal(@Result)^ := GetEnumValue(P, S); 
     end; 
    else 
     raise EArgumentException.CreateFmt('Type %s is not enumeration', [P^.Name]); 
    end; 
end; 
+0

Enumerationen werden als vorzeichenloser Typ gespeichert es nur, so sollten sie nie von 'otSByte',' otSWord' oder 'otSLong' Typ sein (aber es ist nur ein kleiner Nitpick von mir :-) – TLama

+1

Constrain, um Datensatz zu sein, um Klassen zur Kompilierzeit auszuschließen,' T: aufzunehmen ' –

+0

@TLama Ich sehe nicht die Nisse, die du auswählst ;-) Ich würde otSByte usw. aus Gründen der Vollständigkeit hinzufügen. –

3

Ich würde die folgende Variante bieten, eine einfache Erweiterung des Codes aus meiner Antwort auf eine ähnliche Frage: How can I call GetEnumName with a generic enumerated type?

type 
    TEnumeration<T: record> = class 
    strict private 
    class function TypeInfo: PTypeInfo; inline; static; 
    class function TypeData: PTypeData; inline; static; 
    public 
    class function IsEnumeration: Boolean; static; 
    class function ToOrdinal(Enum: T): Integer; inline; static; 
    class function FromOrdinal(Value: Integer): T; inline; static; 
    class function ToString(Enum: T): string; inline; static; 
    class function FromString(const S: string): T; inline; static; 
    class function MinValue: Integer; inline; static; 
    class function MaxValue: Integer; inline; static; 
    class function InRange(Value: Integer): Boolean; inline; static; 
    class function EnsureRange(Value: Integer): Integer; inline; static; 
    end; 

{ TEnumeration<T> } 

class function TEnumeration<T>.TypeInfo: PTypeInfo; 
begin 
    Result := System.TypeInfo(T); 
end; 

class function TEnumeration<T>.TypeData: PTypeData; 
begin 
    Result := TypInfo.GetTypeData(TypeInfo); 
end; 

class function TEnumeration<T>.IsEnumeration: Boolean; 
begin 
    Result := TypeInfo.Kind=tkEnumeration; 
end; 

class function TEnumeration<T>.ToOrdinal(Enum: T): Integer; 
begin 
    Assert(IsEnumeration); 
    Assert(SizeOf(Enum)<=SizeOf(Result)); 
    Result := 0; // needed when SizeOf(Enum) < SizeOf(Result) 
    Move(Enum, Result, SizeOf(Enum)); 
    Assert(InRange(Result)); 
end; 

class function TEnumeration<T>.FromOrdinal(Value: Integer): T; 
begin 
    Assert(IsEnumeration); 
    Assert(InRange(Value)); 
    Assert(SizeOf(Result)<=SizeOf(Value)); 
    Move(Value, Result, SizeOf(Result)); 
end; 

class function TEnumeration<T>.ToString(Enum: T): string; 
begin 
    Result := GetEnumName(TypeInfo, ToOrdinal(Enum)); 
end; 

class function TEnumeration<T>.FromString(const S: string): T; 
begin 
    Result := FromOrdinal(GetEnumValue(TypeInfo, S)); 
end; 

class function TEnumeration<T>.MinValue: Integer; 
begin 
    Assert(IsEnumeration); 
    Result := TypeData.MinValue; 
end; 

class function TEnumeration<T>.MaxValue: Integer; 
begin 
    Assert(IsEnumeration); 
    Result := TypeData.MaxValue; 
end; 

class function TEnumeration<T>.InRange(Value: Integer): Boolean; 
var 
    ptd: PTypeData; 
begin 
    Assert(IsEnumeration); 
    ptd := TypeData; 
    Result := Math.InRange(Value, ptd.MinValue, ptd.MaxValue); 
end; 

class function TEnumeration<T>.EnsureRange(Value: Integer): Integer; 
var 
    ptd: PTypeData; 
begin 
    Assert(IsEnumeration); 
    ptd := TypeData; 
    Result := Math.EnsureRange(Value, ptd.MinValue, ptd.MaxValue); 
end; 

ich es auf meinem Handy getippt so könnte es Arbeit müssen kompilieren . Es bietet, was Sie verlangen und mehr.

Eine Schlüssel Sache, die diese Variante tut, ist, die Umwandlung zwischen enum und ordinal in wiederverwendbare Methoden zu trennen.

1

für meinen Teil denke ich, eine generische Klasse verwenden, um die Aufzählungen stopfen zu implementieren, ist keine gute Idee, denn es gibt zwei Arten von Aufzählungen:

  1. klassisch/true Enum ohne explicites Ordinalwerte, oder die Werte Beginnen Sie bei 0 und jeder Nachfolger entspricht Vorgänger + 1 ("TMyEnum = eins, zwei, drei;"), die korrekt funktionieren

  2. andere/falsche enum mit expliziten Ordnungszahlen nicht beginnend mit 0 oder mit Nachfolger nicht gleich Vorgänger + 1 ("TMyOtherEnum = eins = 1, zwei = 2, drei = 3;"), das wird nicht funktionieren, weil diese Typen RTTI-Informationen nicht bereitstellen an (als Zeiger oder ohne RTTI-Klassen/Schnittstellen). Sie können TypeInfo nicht für diese Typen aufrufen, da der Code nicht kompiliert wird, außer im Falle von Generics. In diesem Fall kann TypeInfo nur nil zurückgeben, da Delphi nicht prüfen kann, ob der Typ RTTI-Informationen zur Kompilierungszeit enthält.

Mit der Implementierung werden Sie sogar Zugriffsverletzung haben, weil Sie nicht, dass „Typeinfo“ <> nil überprüft.

Sie können es natürlich überprüfen und "TypeInfo.Kind = tkEnumeration" überprüfen und ggf. Assertion auslösen, aber ich denke, es ist bei weitem besser, den Fehler zur Kompilierungszeit als zur Laufzeit zu erkennen.Dazu müssen Sie einen zusätzlichen "typeinfo" -Parameter in jeder Ihrer Methode hinzufügen und schließlich generische bringt nicht viel ...

Sie können natürlich all dies ignorieren, wenn Sie nie "andere/falsche enums" verwenden im Code ;-)

1

Irgendwie diese entscheidende Information wird als Antwort fehlt:

in der letzten Delphi-Versionen gibt es keine Notwendigkeit, einen generischen Helfer zu schreiben ist Aufzählungen zu Zeichenfolge konvertieren und zurück, weil es schon da ist in System.Rtti, und tatsächlich ist es sehr ähnlich zu den bestehenden Antworten hier umgesetzt.

class function TRttiEnumerationType.GetName<T{: enum}>(AValue: T): string; 
class function TRttiEnumerationType.GetValue<T{: enum}>(const AName: string): T; 

Verbrauch ist sehr kurz und einfach:

S:= TRttiEnumerationType.GetName(myEnum); 
Verwandte Themen