I\'m trying to write functions that will convert an enumeration to string and back again.
ie:
TConversions = class
strict private
public
You need to fiddle with things a bit. There is no generic for enums so we get around it by casting to and from the enum using Byte, Word and Cardinal.
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.
I'd offer the following variant, a simple extension of the code from my answer to a similar question: 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;
I typed it on my phone so it might need work to compile. It offers what you ask for and more.
One key thing that this variant does is to separate the conversion between enum and ordinal into re-usable methods.
Somehow this crucial information is missing as an answer:
In recent Delphi versions there is no need to write any generic helper to convert enums to string and back, because it is already there in System.Rtti
, and in fact it is implemented very similar to the existing answers here.
class function TRttiEnumerationType.GetName<T{: enum}>(AValue: T): string;
class function TRttiEnumerationType.GetValue<T{: enum}>(const AName: string): T;
Usage is very short and simple:
S:= TRttiEnumerationType.GetName(myEnum);
There seems to be no T:enum
generic type constraint so I think the best you can do is check the type at runtime, something like this:
Edit: Based on David's comment, I've added the T: record
constraint which can be used to constrain to value types (and rule out class types).
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;
For my part I think use a generic class to implement the enums stuffs is not a good idea because there are two kind of enums :
classic/true enum without explicites ordinal values, or the values start from 0 and each successor equals predecessor+1 ("TMyEnum = one, two, three;") that will work correctly
others/fake enum with explicites ordinal values not starting from 0 or with successor not equals predecessor+1 ("TMyOtherEnum = one = 1, two = 2, three = 3;") that won't work because theses types doesn't provide RTTI information (as Pointer or without RTTI classes/interfaces). You can't call TypeInfo on theses types because the code doesn't compile, except in case of generics, in this only case TypeInfo can return nil because Delphi can't check if the type has RTTI info at compile time.
With your implementation you will even have access violation because you doesn't check that "TypeInfo" <> nil.
You can of course check it and check "TypeInfo.Kind = tkEnumeration" and raise assertion if necessary but I think it is by far better to detect the error at compil time than at runtime. For this you need to add an extra "typeinfo" parameter in each of your method and finally generic doesn't bring a lot...
You can of course ignore all this if you never use "other/fake enums" in your code ;-)