Delphi Programming

and software in general.

Friday, April 8, 2011

Generics, Enumerated types and ordinal values

I wish this was a solution post, but it is a frustration post. I trying to figure out how I can use Ord() to convert an enumerated type to integer or cast an integer to an enumerated type using generics type arguments?

uses
  SysUtils,
  TypInfo;

type
  TSomeEnumType = (TheFirst, TheSecond, TheThird, TheFourth);

type
  TEnumGen<TEnumType> = class
    class function Name(const Enum:TEnumType):String;
    class function Value(const Ordinal:Integer):TEnumType;
  end;

class function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String;
begin
  Result := Format('%s.%s', 
    [GetTypeName(TypeInfo(TEnumType)), 
     GetEnumName(TypeInfo(TEnumType), Ord(Enum)) // <-- Bombs
    ]);
end;

class function TEnumGen<TEnumType>.Value(const Ordinal:Integer):TEnumType;
begin
  Result := TEnumType(Ordinal); // <- Bombs
end;

Unfortunately there is no "enum" delimiter that can be used to tell the compiler that Ord() and casting of integers should be allowed for generic enumerated type arguments.

10 comments:

  1. This should work (assuming Blogger doesn't eat it for dinner):

    class function TEnumGen.Name(const Enum: TEnumType): String;
    var
    Info: PTypeInfo;
    OrdValue: Integer;
    begin
    Info := TypeInfo(TEnumType);
    case GetTypeData(Info).OrdType of
    otSByte: OrdValue := PShortInt(@Enum)^;
    otUByte: OrdValue := PByte(@Enum)^;
    otSWord: OrdValue := PWord(@Enum)^;
    otUWord: OrdValue := PSmallInt(@Enum)^;
    otSLong: OrdValue := PLongWord(@Enum)^;
    otULong: OrdValue := PLongInt(@Enum)^;
    else raise EProgrammerNotFound.Create('New TOrdType element!');
    end;
    Result := Format('%s.%s',
    [GetTypeName(Info), GetEnumName(Info, OrdValue)]);
    end;

    class function TEnumGen.Value(const Ordinal:Integer):TEnumType;
    begin
    Move(Ordinal, Result, SizeOf(TEnumType));
    end;

    ReplyDelete
  2. Blogger was indeed hungry I see, if hardly famished... Obviously, it would be better if there were an ordinal type constraint (along with operator constraints and so on), but my suggested solution is quite general.

    ReplyDelete
  3. Thanks, Chris. That will have to suffice for now, and it will get me by for this little tool class I am carving out atm.

    ReplyDelete
  4. TValue is your friend! :)


    class function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String;
    var
      i: Int64;
    begin
      TValue.From<TEnumType>(Enum).TryAsOrdinal(i);
      Result := Format('%s.%s', [GetTypeName(TypeInfo(TEnumType)), GetEnumName(TypeInfo(TEnumType), i)]);
    end;

    class function TEnumGen<TEnumType>.Value(const Ordinal:Integer): TEnumType;
    var
      v: TValue;
    begin
      TValue.Make(Ordinal, TypeInfo(TEnumType), v);
      Result := v.AsType<TEnumType>;
    end;

    ReplyDelete
  5. Stefan - nice! In fact, that allows doing away with an explicit call to GetEnumName entirely -

    class function TEnumGen<TEnumType>.Name(const Enum: TEnumType): String;
    var
      Value: TValue;
    begin
      TValue.From<TEnumType>(Enum);
      Result := Format('%s.%s', [GetTypeName(Value.TypeInfo), Value.ToString]);
    end;

    ReplyDelete
  6. Oh yes, even better that way, Chris.
    And you can also do it like this:
    Format('%s.%s', [v.TypeInfo.Name, v.ToString]);

    I really like TValue :)

    ReplyDelete
  7. Sweet! This is almost clean code ;)

    I wonder - how much overhead accessing through TValue is, compared to a "native" access.

    ReplyDelete
  8. Hi,

    Had a similar need when started with generics back with RAD 2009 and solved the cast this way:

    class function Generic.EnumToInt(const EnumValue: T): Integer;
    begin
    Result := 0;
    Move( EnumValue, Result, sizeOf(EnumValue) );
    end;

    class function Generic.IntToEnum(const IntValue: Integer): T;
    begin
    Move( IntValue, Result, SizeOf(Result) );
    end;

    which can be used as follows:

    class function Generic.StringToEnum(StringValue: string): T;
    begin
    Result := Generic.IntToEnum(getEnumValue( TypeInfo(T), StringValue ));
    end;

    class function Generic.EnumToString(EnumValue: T): string;
    begin
    Result := getEnumName( TypeInfo(T), Generic.EnumToInt(EnumValue) );
    end;

    but I agree TValue seems a more clean solution.

    ReplyDelete
  9. Take a look at http://cc.embarcadero.com/item/27397

    TEnum

    type
    TJim = (jjHigh,jjLow,jjHello_There);
    var
    J: TEnum;
    S: String;
    Strs: TStrings;
    I: Integer;
    begin
    // Implicit assign of the friendly string
    J := 'Hello There';

    // implicit assign of enumorated name as
    J := 'jjHello_There'; string

    // implicit ordinal assign
    J := 2;

    // implicit assign of enumoratedtype
    J := jjHello_There;

    // This uses casting to select the proper implicit conversion
    WriteLn(String(J),'(',Integer(I),')');
    // Output should be 'Hello There(2)'

    // Add the enumorated type value to TStrings
    Strs.Add(J);
    end.

    ReplyDelete
  10. Nice!
    However, for a variable T of any enumerated type, I only needed T -> ordinal, and ordinal -> T.

    ReplyDelete