Generic functions for converting an enumeration to string and back

后端 未结 5 1684
终归单人心
终归单人心 2020-12-11 01:33

I\'m trying to write functions that will convert an enumeration to string and back again.

ie:

TConversions = class
    strict private
    public
            


        
相关标签:
5条回答
  • 2020-12-11 01:42

    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.
    
    0 讨论(0)
  • 2020-12-11 01:45

    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.

    0 讨论(0)
  • 2020-12-11 01:47

    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);
    
    0 讨论(0)
  • 2020-12-11 01:49

    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;
    
    0 讨论(0)
  • 2020-12-11 02:00

    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 :

    1. 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

    2. 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 ;-)

    0 讨论(0)
提交回复
热议问题