Общие функции для преобразования перечисления в строку и обратно

Я пытаюсь написать функции, которые преобразуют перечисление в строку и обратно.

т.е.:

TConversions = class
    strict private
    public
      class function StringToEnumeration<T:class>(x:String):T;
      class function EnumerationToString<T:class>(x:T):String;
  end;

в разделе реализации у меня есть

uses
System.TypInfo
;

class function TConversions.StringToEnumeration<T>(x:String):T;
begin
    Result :=  T(GetEnumValue(TypeInfo(T), x));
end;

class function TConversions.EnumerationToString<T>(x:T):String;
begin
    Result := GetEnumName(TypeInfo(T), integer(x));
end;

Проблема в том, что перечисление не имеет типа T:class в паскале. Я не могу использовать T:record.

Можно ли это сделать в pascal?

Ответ 1

Вам нужно немного поиграть с вещами. Для перечислений не существует общего типа, поэтому мы обходим его, перебрасывая в перечисление и из перечисления с помощью Byte, Word и 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.

Ответ 2

Похоже, что ограничение типа T:enum общего типа не существует, поэтому я думаю, что лучше всего вы можете проверить тип во время выполнения, что-то вроде этого:

Изменить. Основываясь на комментарии Дэвида, я добавил ограничение T: record, которое может использоваться для ограничения типов значений (и исключения из типов классов).

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;

Ответ 3

Я бы предложил следующий вариант, простое расширение кода из моего ответа на аналогичный вопрос: Как я могу вызвать GetEnumName с общим перечисляемым типом?

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;

Я набрал его на своем телефоне, поэтому может понадобиться компиляция. Он предлагает то, о чем вы просите, и многое другое.

Одним из ключевых моментов, которые делает этот вариант, является разделение преобразования между enum и порядком в повторно используемые методы.

Ответ 4

Как-то эта важная информация отсутствует в качестве ответа:

В последних версиях Delphi нет необходимости писать какой-либо универсальный помощник для преобразования перечислений в строку и обратно, потому что он уже существует в System.Rtti, и на самом деле он очень похож на существующие ответы здесь.

class function TRttiEnumerationType.GetName<T{: enum}>(AValue: T): string;
class function TRttiEnumerationType.GetValue<T{: enum}>(const AName: string): T;

Использование очень короткое и простое:

S:= TRttiEnumerationType.GetName(myEnum);

Ответ 5

С моей стороны, я думаю, что использовать универсальный класс для реализации ресурсов enums - это не очень хорошая идея, потому что есть два вида перечислений:

  • классический/истинный enum без объяснительных порядковых значений, или значения начинаются с 0, и каждый преемник равен предшественнику + 1 ( "TMyEnum = один, два, три;" ), который будет работать правильно

  • other/fake enum с порядковыми значениями explicites, не начиная с 0, или с преемником не равно предшественнику + 1 ( "TMyOtherEnum = one = 1, two = 2, three = 3;" ), который не будет работать, потому что типы тезисов не предоставляют информацию RTTI (в качестве указателя или без классов/интерфейсов RTTI). Вы не можете вызывать TypeInfo по типам тезисов, потому что код не компилируется, за исключением случаев с генериками, в этом случае TypeInfo может возвращать nil, потому что Delphi не может проверить, имеет ли тип RTTI информацию во время компиляции.

С вашей реализацией у вас даже будет нарушение прав доступа, потому что вы не проверяете, что "TypeInfo" < > nil.

Вы можете, конечно, проверить это и проверить "TypeInfo.Kind = tkEnumeration" и при необходимости повысить утверждение, но я думаю, что намного лучше обнаружить ошибку во время компиляции, чем во время выполнения. Для этого вам нужно добавить дополнительный параметр "typeinfo" в каждом из ваших методов, и, наконец, общий не приносит много...

Конечно, вы можете игнорировать все это, если вы никогда не используете "другие/фальшивые перечисления" в своем коде; -)