2015-07-24 28 views
10

Próbuję napisać funkcje, które skonwertują wyliczenie na ciąg znaków iz powrotem.Funkcje ogólne do konwertowania wyliczenia na ciąg i powrót

tj:

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

w dziale realizacji Mam

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; 

Problem polega na tym, enum nie jest typu T:class w Pascalu. Nie mogę też użyć T:record.

Czy można to zrobić w paśmie?

+0

http://www.thedelphigeek.com/2013/03/using-generics-to-manipulate-enumerable.html?m=1 –

+1

W jednostce Rtti są 'TRttiEnumerationType.GetName (AValue: T): string;' i 'TRttiEnumerationType.GetValue (const AName: string): T;' –

+0

Nowsza wersja (nie wiem od kiedy) pozwala na użycie 'record 'as a bound (dla wszystkich typów wartości, łącznie z enumami):' TSomeEnumThingy ' –

Odpowiedz

9

Potrzebujesz trochę skrzypiec. Nie ma generycznych tematów do wyliczenia, więc poruszamy się dookoła poprzez rzucanie do i wyliczanie za pomocą Byte, Word i 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. 
+1

Co się stanie, jeśli zadzwonisz np. 'TConversions .EnumerationToString (42)'? Nie masz sprawdzenia, czy typ jest rzeczywiście wyliczeniem i nie jestem całkowicie pewien, co dzieje się wewnątrz funkcji 'GetEnumName' (nie ma źródła ręcznie). – TLama

+0

@TLama TConversions .EnumerationToString (42) zwraca 42 :) - Masz jednak rację. Dodam trochę sprawdzania błędów. – Graymatter

+0

@Graymatter jak wdrożyłeś sprawdzanie błędów? Bardzo interesuje mnie rozwiązanie, które napisałeś (w gruncie rzeczy głosowałem nad nim), odkąd jestem na Delphi XE3, gdzie z jakiegoś dziwnego powodu niektóre funkcje klasy TRttiEnumerationType są prywatne i nie mam do nich dostępu. W Delphi => XE5 są one w sekcji interfejsu publicznego –

6

Wydaje się, że nie T:enum rodzajowe typu ograniczenie więc myślę, że to najlepsze co możesz zrobić, to sprawdzić typ w czasie wykonywania, coś takiego:

Edycja: Na podstawie komentarza Dawida Dodałem T: record Ograniczenie, które może być używane do ograniczania typów wartości (i wykluczania typów klas).

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

Wyliczenia są przechowywane jako niepodpisane es tylko, więc nie powinny one nigdy należeć do 'otSByte',' otSWord' ani 'otSLong' type (ale to tylko niewielka nitpick mojej :-) – TLama

+1

Względem rekordu, aby wykluczyć klasy podczas kompilacji,' T: record ' –

+0

@TLama Nie widzę nitu, który wybierasz ;-) W dalszym ciągu dodawałbym otSByte itp. Ze względu na kompletność. –

3

Chciałbym zaoferować następujący wariant, proste rozszerzenie kodu z mojej odpowiedzi na podobne pytanie: 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; 

Wpisałem go na moim telefonie, więc może zaistnieć potrzeba pracy, aby skompilować . Oferuje to, o co prosisz i więcej.

Jedną z kluczowych rzeczy, która polega na tym wariancie, jest oddzielenie konwersji między wyliczeniem a porządkowym na metody wielokrotnego użytku.

1

Z mojej strony myślę użyć rodzajowe klasy do wdrożenia wyliczenia spożywczych nie jest dobrym pomysłem, ponieważ istnieją dwa rodzaje teksty stałe:

  1. klasyczny/true enum bez explicites wartości porządkowych lub wartości zaczyna się od 0, a każdy następca jest równy poprzednikowi + 1 ("TMyEnum = jeden, dwa, trzy;"), który będzie działał poprawnie

  2. inne/fałszywe wyliczenie z explicites wartości porządkowe nie zaczynające się od 0 lub z następcą nie równe poprzednikowi + 1 ("TMyOtherEnum = jeden = 1, dwa = 2, trzy = 3;"), który nie zadziała, ponieważ typy te nie dostarczają informacji RTTI on (jako wskaźnik lub bez klas/interfejsów RTTI). Nie można wywołać TypeInfo na tych typach, ponieważ kod się nie kompiluje, z wyjątkiem przypadku generycznych, w tym jedynym przypadku TypeInfo może zwrócić zero, ponieważ Delphi nie może sprawdzić, czy typ ma informacje RTTI w czasie kompilacji.

Dzięki implementacji będzie nawet naruszenie zasad dostępu, ponieważ nie sprawdza, że ​​„TypeInfo” <> nil.

Oczywiście można to sprawdzić i sprawdzić "TypeInfo.Kind = tkEnumeration" i w razie potrzeby podnieść asercję, ale myślę, że znacznie lepiej jest wykryć błąd w czasie kompilacji niż w czasie wykonywania.W tym celu musisz dodać dodatkowy parametr "typeinfo" w każdej ze swoich metod, a na końcu generyczny nie przynosi dużo ...

Możesz oczywiście zignorować to wszystko, jeśli nigdy nie użyjesz "innych/fałszywych enum" w kodzie ;-)

1

Jakoś to kluczowa informacja brakuje jako odpowiedź:

w ostatnich wersjach Delphi nie ma potrzeby pisać żadnego rodzajowe pomocnika do konwertowania teksty stałe do łańcucha i z powrotem, bo to już jest w System.Rtti, a w rzeczywistości jest on zaimplementowany bardzo podobnie do istniejących odpowiedzi tutaj.

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

Użycie jest bardzo krótka i prosta:

S:= TRttiEnumerationType.GetName(myEnum);