2013年8月13日

列挙型と列挙子名(文字列)または整数の相互変換(ジェネリックス版)

しばらく前にジェネリックス版の列挙型と列挙子名の相互変換について書きましたが、これを多少改善してみました。まず整数から列挙値への変換を追加しました(GetEnumValueのInteger引数版)。整数から列挙値への変換は普通は型キャストですませてしまいますが、これだと(デフォルトの設定である){$RANGECHECKS OFF}の状態で範囲外の値が格納されることを防げないため、列挙型の最小値、最大値の確認を行うようにしています。あとはエラーが発生したときに例外を生成するのではなく戻値で区別する関数(Try...)を追加しました。
uses
  TypInfo, SysUtils, SysConst;

type
  TEnumHelper = record
    class function TryGetEnumName<T: record>(Value: T; out S: String): Boolean; static;
    class function GetEnumName<T: record>(Value: T): String; static;
    class function TryGetEnumValue<T: record>(const Name: String; out Enum: T): Boolean; overload; static;
    class function GetEnumValue<T: record>(const Name: String): T; overload; static;
    class function TryGetEnumValue<T: record>(Value: Integer; out Enum: T): Boolean; overload; static;
    class function GetEnumValue<T: record>(Value: Integer): T; overload; static;
  end;

class function TEnumHelper.TryGetEnumName<T>(Value: T; out S: String): Boolean;
var
  P: PTypeInfo;
  IValue: Integer;
begin

  Result := False;
  S := '';

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    Exit;
  end;

  IValue := 0;
  Move(Value,IValue,SizeOf(T));
  S := TypInfo.GetEnumName(P,IValue);
  Result := True;

end;

class function TEnumHelper.GetEnumName<T>(Value: T): String;
var
  P: PTypeInfo;
  IValue: Integer;
begin

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    raise EInvalidOpException.CreateRes(@SVarNotImplemented);
  end;

  IValue := 0;
  Move(Value,IValue,SizeOf(T));
  Result := TypInfo.GetEnumName(P,IValue);

end;

class function TEnumHelper.TryGetEnumValue<T>(const Name: String; out Enum: T): Boolean;
var
  P: PTypeInfo;
  IValue: Integer;
begin

  Result := False;
  Enum := Default(T);

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    Exit;
  end;

  IValue := TypInfo.GetEnumValue(P,Name);

  with GetTypeData(P)^ do
  begin
    if (IValue < MinValue) or (IValue > MaxValue) then
    begin
      Exit;
    end;
  end;

  Move(IValue,Enum,SizeOf(T));
  Result := True;

end;

class function TEnumHelper.GetEnumValue<T>(const Name: String): T;
var
  P: PTypeInfo;
  IValue: Integer;
begin

  Result := Default(T);

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    raise EInvalidOpException.CreateRes(@SVarNotImplemented);
  end;

  IValue := TypInfo.GetEnumValue(P,Name);

  with GetTypeData(P)^ do
  begin
    if (IValue < MinValue) or (IValue > MaxValue) then
    begin
      raise ERangeError.CreateRes(@SRangeError);
    end;
  end;

  Move(IValue,Result,SizeOf(T));

end;

class function TEnumHelper.TryGetEnumValue<T>(Value: Integer; out Enum: T): Boolean;
var
  P: PTypeInfo;
begin

  Result := False;
  Enum := Default(T);

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    Exit;
  end;

  with GetTypeData(P)^ do
  begin
    if (Value < MinValue) or (Value > MaxValue) then
    begin
      Exit;
    end;
  end;

  Move(Value,Enum,SizeOf(T));
  Result := True;

end;

class function TEnumHelper.GetEnumValue<T>(Value: Integer): T;
var
  P: PTypeInfo;
begin

  Result := Default(T);

  P := TypeInfo(T);
  if (P = nil) or (P^.Kind <> tkEnumeration) then
  begin
    raise EInvalidOpException.CreateRes(@SVarNotImplemented);
  end;

  with GetTypeData(P)^ do
  begin
    if (Value < MinValue) or (Value > MaxValue) then
    begin
      raise ERangeError.CreateRes(@SRangeError);
    end;
  end;

  Move(Value,Result,SizeOf(T));

end;
こんな感じで使います。
var
  S: String;
begin

  if TEnumHelper.TryGetEnumName(0,S) = True then  // Error (0 is not enumeration)
  begin
    Label1.Caption := S;
  end
  else
  begin
    Label1.Caption := '(Error)';
  end;

  S := TEnumHelper.GetEnumName(taLeftJustify);  // taLeftJustify -> 'taLeftJustify'
  Label2.Caption := S;

  S := TEnumHelper.GetEnumName(False);  // False -> 'False'
  Label3.Caption := S;

end;
元ねたはDelphi XE2 Foundations

列挙型と列挙子名(文字列)または整数の相互変換(ジェネリックス版) (Gist)

0 件のコメント: