I have a Generic class which uses an Enum Generic Type. My problem how do I use GetEnumName on an instance of that type?
I've created a small demo class to illustrate the problem:
type
TEnumSettings<TKey: record > = class
private
Key: TKey;
public
constructor Create(aKey: TKey);
function ToString: string; override;
end;
uses
TypInfo;
{ TEnumSettings<TKey> }
constructor TEnumSettings<TKey>.Create(aKey: TKey);
begin
if PTypeInfo(System.TypeInfo(TKey)).Kind <> tkEnumeration then
Exception.Create(string(PTypeInfo(System.TypeInfo(TKey)).Name) + ' is not an Enumeration');
Key := aKey;
end;
function TEnumSettings<TKey>.ToString: string;
begin
Result := GetEnumName(System.TypeInfo(TKey), Integer(Key)) <== HERE I get a compile error: Invalid type cast
end;
I'm using Delphi XE. So can this be done? And if so how?
Personally, I would do this with a call to Move
. I have the following 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 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>.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;
The ToOrdinal
method does what you need, and I'm sure you'll be able to adapt it to your class.
If you don't like using Move
in this way, then you can use TValue
.
TValue.From<TKey>(Key).AsOrdinal
And @TLama points out that you can avoid calling GetEnumName
at all by using
TValue.From<TKey>(Key).ToString
On the face of it, using TValue
seems to be more in keeping with the ethos of generics and RTTI. A call to Move
relies on the specific implementation details of enumerated types. However, it's quite interesting to step through the debugger and observe quite how much code is involved in executing TValue.From<TKey>(Key).AsOrdinal
. That alone is enough to make me hesitate to recommend using TValue
.
Yet another way to achieve this is to use TRttiEnumerationType
:
TRttiEnumerationType.GetName<TKey>(Key)
The implementation of this is much more efficient than using TValue.ToString
, being little more than a call to GetEnumName
.
This is an updated version af my class, with the change suggested. Thanks to David and TLama
uses
TypInfo, Rtti;
type
TEnumSettings<TKey: record> = class
private
Key: TKey;
public
constructor Create(aKey: TKey);
function ToString: string; override;
end;
{ TEnumSettings<TKey> }
constructor TEnumSettings<TKey>.Create(aKey: TKey);
begin
if PTypeInfo(System.TypeInfo(TKey)).Kind <> tkEnumeration then
raise Exception.Create(string(PTypeInfo(System.TypeInfo(TKey)).Name) + ' is not an Enumeration');
Key := aKey;
end;
function TEnumSettings<TKey>.ToString: string;
begin
Result := TValue.From<TKey>(Key).ToString;
end;
And a little test example :
Copy the code into OnCreate of a From:
procedure TForm1.FormCreate(Sender: TObject);
begin
with TEnumSettings<boolean> .Create(True) do
try
Caption := ToString;
finally
Free;
end;
end;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With