Swanman's Horizon

性欲をもてあましつつなんらかの話をするよ。

ジェネリクスを使って少しだけ手軽に列挙型の値を文字列に変換する。

これとは関係ないネタを書いてたんだけど、全然まとまりそうにないのでわりとどうでもいいネタでお茶を濁す

列挙型を文字列に

例えば列挙型の値をiniファイルなどに保存したい場合、Ordで整数値を取り出す、Integerでキャストする、という選択肢のほかに、列挙型をそのまま文字列化するという方法があります。
古くからあるRTTIを利用する方法で、コードとしてはこんな感じのものです。

S := GetEnumName(TypeInfo(TAlign), Integer(alTop)); // S => 'alTop'

型名が必要なんですって

この方法は型名を書かなくてはいけないだけでなく、RTTIを取り出すためのTypeInfoやIntegerによるキャストも毎回毎回書かなくてはならず、結構面倒です。
(個人的にはそこまで面倒だと思ったことはないですが、ここは面倒という方向でひとつ…)
そこで、ジェネリクスを使ってとりあえずTypeInfoとIntegerを書かずに済むようにしてみます。

uses
  TypInfo;

type
  TEnum = class
  public
    class function GetName<T>(const Value: T): string; static;
  end;

class function TEnum.GetName<T>(const Value: T): string;
var
  Info: PTypeInfo;
  Data: PTypeData;
  I: Integer;
begin
  // 列挙型では型制約ができないので、列挙型以外を渡されたら仕方なく型名を返す
  Info := TypeInfo(T);
  if Info = nil then Exit('');
  if Info^.Kind = tkEnumeration then
  begin
    I := 0;
    Move(Value, I, SizeOf(T));
    Result := GetEnumName(Info, I);
  end
  else begin
    Result := string(Info^.Name);
  end;
end;

このクラスさえ用意しておけば、あとは以下のようにシンプルに書くことができます。

S := GetEnumName(TypeInfo(TAlign), Integer(alTop)); // これが

S := TEnum.GetName<TAlign>(alTop); // こうなる

やった!第三部完!

といきたいところですが、このコード、実はさらに短くできます。
クラスではなくメソッドにが付くこのジェネリックなメソッド、公式的には「パラメータ化メソッド」と言うそうですが、そのヘルプにはこんな一文があります。

メソッドをインスタンス化する方法には次の 2 つがあります。
明示的な型引数の指定
引数型からの自動的な推論
http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B8%E3%82%A7%E3%83%8D%E3%83%AA%E3%83%83%E3%82%AF%E3%82%B9%E3%81%AE%E5%AE%A3%E8%A8%80

そう、実はDelphiでも非常に限定的ながら型推論ができるんです。つまり上記のコードは型指定を省略してこのように書けます。

S := TEnum.GetName(alTop);

元のコードと比べるともはや半分以下です。

本日のまとめ

ヘルプもたまには役に立つ。