Jump to content

Recommended Posts

4 hours ago, Lars Fosdal said:

Translate: array[TAllowState] of xlt = ( { asAllow } (no:'Tillatt'; se:'Tillåten'; en:'Allow'), { asWarn } (no:'Vis advarsel'; se:'Visa varning'; en:'Show warning'), { asDeny } (no:'Sperret'; se:'Sperrad'; en:'Denied') );

Your example gave me an idea, that I completely missed. I don't need 2 consts, 1 should be enough:

 

const
  cProjectTypeNames: array[TProjectType] of TTypeAsName =  (
      {ptMain}     (DisplayName: 'Main Project';     XMLName: 'xml_main'),
      {ptExternal} (DisplayName: 'External Project'; XMLName: 'xml_external')
    );

This is then easier to extend, adding additional name, and if there are many values, which you would split into multiple lines in code, the names are aligned and you can't make a mistake.


The only drawback is, if you have 10 values, this becomes 10+2 lines, while before it was 4 lines for 2 consts, total.

  • Like 1

Share this post


Link to post
  // ***************************************************************************
  // Static class to convert enum value <=> enum name
  // ***************************************************************************

  TEnum<T> = class
  strict private
    class var
      FPTypInf: PTypeInfo;
      FMin, FMax: Integer;
    {$IFDEF CAPS_CLASSCONSTROK}
    class constructor Create;
    {$ELSE}
    class procedure Init;
    {$ENDIF}
    class procedure CheckRange(Item: Integer); inline;
  public
    // T => Int
    class function Int(Item: T): Integer; overload; inline;
    // Str => Int
    class function Int(const Name: string): Integer; overload; inline;
    // Int => T
    class function Val(Item: Integer; CheckRange: Boolean = True): T; overload; inline;
    // Str => T
    class function Val(const Name: string): T; overload; inline;
    // T => Str
    class function Str(Item: T): string; overload; inline;
    // Int => Str
    class function Str(Item: Integer): string; overload;
    // Search for Item in Values array and return its index as T
    class function Find(const Item: string; const Values: array of string): T; overload;
    class function Find(const Item: Char; const Values: array of Char): T; overload;

    class property Min: Integer read FMin;
    class property Max: Integer read FMax;
  end;

{$REGION 'TEnum<T>'}

{$IFDEF TYPES_GENERICS}

{$IFDEF CAPS_CLASSCONSTROK}
// Perform some checks and save type properties.
// Executed on unit init if the class is used, raises exception if type is invalid.
class constructor TEnum<T>.Create;
{$ELSE}
class procedure TEnum<T>.Init;
{$ENDIF}
begin
  FPTypInf := PTypeInfo(TypeInfo(T));
  // type info check
  if FPTypInf = nil then
    raise Err(S_E_NoTypeInfo);
  // run-time type check
  if FPTypInf.Kind <> tkEnumeration then
    raise Err(S_EEnum_NotAnEnum, [FPTypInf.Name]);
  // get range
  FMin := GetTypeData(FPTypInf).MinValue;
  FMax := GetTypeData(FPTypInf).MaxValue;
end;

// Check if Item in enum range
class procedure TEnum<T>.CheckRange(Item: Integer);
begin
  if (Item < FMin) or (Item > FMax) then
    raise Err(S_EEnum_NotInRange, [Item, FMin, FMax, FPTypInf.Name]);
end;

// Integer => Enum member, the same as Integer(T)
//   CheckRange: controls whether checking if Item belongs Low(T)..High(T) will
//     be performed. Useful to return T(-1) as invalid value.
class function TEnum<T>.Val(Item: Integer; CheckRange: Boolean): T;
var p: Pointer;
begin
  {$IFNDEF CAPS_CLASSCONSTROK}
  if FPTypInf = nil then
    Init;
  {$ENDIF}
  if CheckRange then
    Self.CheckRange(Item);

  p := @Result;

  case SizeOf(T) of
    1: PUInt8(p)^  := UInt8(Item);
    2: PUInt16(p)^ := UInt16(Item);
    else
      raise Err(S_EEnum_WrongSize, [SizeOf(T)]);
  end;
end;

// Enum member => Integer, the same as T(Int)
class function TEnum<T>.Int(Item: T): Integer;
var p: Pointer;
begin
  p := @Item;

  case SizeOf(T) of
    1: Result := PUInt8(p)^ ;
    2: Result := PUInt16(p)^;
    else
      raise Err(S_EEnum_WrongSize, [SizeOf(T)]);
  end;
end;

// Integer => String
class function TEnum<T>.Str(Item: Integer): string;
begin
  {$IFNDEF CAPS_CLASSCONSTROK}
  if FPTypInf = nil then
    Init;
  {$ENDIF}
  CheckRange(Item);
  Result := GetEnumName(FPTypInf, Item);
end;

// T => String
class function TEnum<T>.Str(Item: T): string;
begin
  Result := Str(Int(Item));
end;

// String => Integer
class function TEnum<T>.Int(const Name: string): Integer;
begin
  {$IFNDEF CAPS_CLASSCONSTROK}
  if FPTypInf = nil then
    Init;
  {$ENDIF}
  Result := GetEnumValue(FPTypInf, Name);
  if Result = -1 then
    raise Err(S_EEnum_NoValueForName, [Name, FPTypInf.Name]);
end;

// String => T
class function TEnum<T>.Val(const Name: string): T;
begin
  Result := Val(Int(Name));
end;

// Find string representation in array of strings and return T
// Similar to Val(Str) but Text and Values could be arbitrary.
// Returns T(-1) if Text not found
class function TEnum<T>.Find(const Item: string; const Values: array of string): T;
begin
  Result := Val(FindStr(Item, Values), False); // Turn off range check to return -1
end;

// The same but for Chars
class function TEnum<T>.Find(const Item: Char; const Values: array of Char): T;
begin
  Result := Val(FindChar(Item, Values), False); // Turn off range check to return -1
end;

{$ENDIF}

{$ENDREGION}


// usage

TEnum<TSomeEnum>.Str(seFirst) => 'seFirst'
TEnum<TSomeEnum>.Val('seFirst') => seFirst
TEnum<TSomeEnum>.Find('First', ['Fisrt', 'Second']) => seFirst

Just note that only "naturally numbered" enums have type info (if any of elements has explicit index assignment, no type info is generated)

  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×