uses SysUtils, TypInfo; type TSomeEnumType = (TheFirst, TheSecond, TheThird, TheFourth); type TEnumGen<TEnumType> = class class function Name(const Enum:TEnumType):String; class function Value(const Ordinal:Integer):TEnumType; end; class function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String; begin Result := Format('%s.%s', [GetTypeName(TypeInfo(TEnumType)), GetEnumName(TypeInfo(TEnumType), Ord(Enum)) // <-- Bombs ]); end; class function TEnumGen<TEnumType>.Value(const Ordinal:Integer):TEnumType; begin Result := TEnumType(Ordinal); // <- Bombs end;
Unfortunately there is no "enum" delimiter that can be used to tell the compiler that Ord() and casting of integers should be allowed for generic enumerated type arguments.
This should work (assuming Blogger doesn't eat it for dinner):
ReplyDeleteclass function TEnumGen.Name(const Enum: TEnumType): String;
var
Info: PTypeInfo;
OrdValue: Integer;
begin
Info := TypeInfo(TEnumType);
case GetTypeData(Info).OrdType of
otSByte: OrdValue := PShortInt(@Enum)^;
otUByte: OrdValue := PByte(@Enum)^;
otSWord: OrdValue := PWord(@Enum)^;
otUWord: OrdValue := PSmallInt(@Enum)^;
otSLong: OrdValue := PLongWord(@Enum)^;
otULong: OrdValue := PLongInt(@Enum)^;
else raise EProgrammerNotFound.Create('New TOrdType element!');
end;
Result := Format('%s.%s',
[GetTypeName(Info), GetEnumName(Info, OrdValue)]);
end;
class function TEnumGen.Value(const Ordinal:Integer):TEnumType;
begin
Move(Ordinal, Result, SizeOf(TEnumType));
end;
Blogger was indeed hungry I see, if hardly famished... Obviously, it would be better if there were an ordinal type constraint (along with operator constraints and so on), but my suggested solution is quite general.
ReplyDeleteThanks, Chris. That will have to suffice for now, and it will get me by for this little tool class I am carving out atm.
ReplyDeleteTValue is your friend! :)
ReplyDeleteclass function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String;
var
i: Int64;
begin
TValue.From<TEnumType>(Enum).TryAsOrdinal(i);
Result := Format('%s.%s', [GetTypeName(TypeInfo(TEnumType)), GetEnumName(TypeInfo(TEnumType), i)]);
end;
class function TEnumGen<TEnumType>.Value(const Ordinal:Integer): TEnumType;
var
v: TValue;
begin
TValue.Make(Ordinal, TypeInfo(TEnumType), v);
Result := v.AsType<TEnumType>;
end;
Stefan - nice! In fact, that allows doing away with an explicit call to GetEnumName entirely -
ReplyDeleteclass function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String;
var
Value: TValue;
begin
TValue.From<TEnumType>(Enum);
Result := Format('%s.%s', [GetTypeName(Value.TypeInfo), Value.ToString]);
end;
Oh yes, even better that way, Chris.
ReplyDeleteAnd you can also do it like this:
Format('%s.%s', [v.TypeInfo.Name, v.ToString]);
I really like TValue :)
Sweet! This is almost clean code ;)
ReplyDeleteI wonder - how much overhead accessing through TValue is, compared to a "native" access.
Hi,
ReplyDeleteHad a similar need when started with generics back with RAD 2009 and solved the cast this way:
class function Generic.EnumToInt(const EnumValue: T): Integer;
begin
Result := 0;
Move( EnumValue, Result, sizeOf(EnumValue) );
end;
class function Generic.IntToEnum(const IntValue: Integer): T;
begin
Move( IntValue, Result, SizeOf(Result) );
end;
which can be used as follows:
class function Generic.StringToEnum(StringValue: string): T;
begin
Result := Generic.IntToEnum(getEnumValue( TypeInfo(T), StringValue ));
end;
class function Generic.EnumToString(EnumValue: T): string;
begin
Result := getEnumName( TypeInfo(T), Generic.EnumToInt(EnumValue) );
end;
but I agree TValue seems a more clean solution.
Take a look at http://cc.embarcadero.com/item/27397
ReplyDeleteTEnum
type
TJim = (jjHigh,jjLow,jjHello_There);
var
J: TEnum;
S: String;
Strs: TStrings;
I: Integer;
begin
// Implicit assign of the friendly string
J := 'Hello There';
// implicit assign of enumorated name as
J := 'jjHello_There'; string
// implicit ordinal assign
J := 2;
// implicit assign of enumoratedtype
J := jjHello_There;
// This uses casting to select the proper implicit conversion
WriteLn(String(J),'(',Integer(I),')');
// Output should be 'Hello There(2)'
// Add the enumorated type value to TStrings
Strs.Add(J);
end.
Nice!
ReplyDeleteHowever, for a variable T of any enumerated type, I only needed T -> ordinal, and ordinal -> T.