How can I call GetEnumName with a generic enumerated type?

后端 未结 2 1126
小鲜肉
小鲜肉 2021-01-05 08:54

I have a Generic class wich 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 illustra

相关标签:
2条回答
  • 2021-01-05 09:02

    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.

    0 讨论(0)
  • 2021-01-05 09:17

    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;
    
    0 讨论(0)
提交回复
热议问题