初期化を行うコードの文字列表現を生成する

諸事情から、与えられた値に初期化するコードの文字列を生成することになった。たとえば、System.Drawing.Colorの場合、値は次の4通りの方法で初期化される。

  1. System.Drawing.Color.Empty
  2. System.Drawing.SystemColors.*
  3. System.Drawing.Color.*
  4. System.Drawing.Color.FromArgb

この場合、2番目のものを除けば、System.Drawing.Color.FromArgbで生成できるものの、SystemColorsのものはどうしようもない。さらに、特定の型なら決め打ちすればいいのだが、他の型の場合も考えるとその方法は取れない。

で、色々と調べていたところ、System.ComponentModel.Design.Serialization.InstanceDescriptorクラスに変換するとこの手の初期化処理をやってくれることがわかった。
型に依存しないように注意すると、

object value; // 対象の値

TypeConverter converter = TypeDescriptor.GetConverter(value);
InstanceDescriptor descriptor = (InstanceDescriptor)converter.ConvertTo(
  value, typeof(InstanceDescriptor));

次の問題は、このInstanceDescriptorをどのように文字列に直すかだ。文字列に直すというと、System.CodeDom.Compiler.CodeDomProviderだ。

CodeExpression expression // 対象の式
using (StringWriter writer = new StringWriter())
{
  CSharpCodeProvider provider = new CSharpCodeProvider();
  provider.GenerateCodeFromExpression(expression, writer,
    new CodeGeneratorOptions());
  return writer.GetStringBuilder().ToString();
}

これで文字列表現が得られた。

さて、残った部分は、InstanceDescriptorからCodeExpressionを生成する部分だ。ここがなかなかわからなかった。それらしいクラスも見つからなかったので、自分で作ってみた。
初期化に必要そうな式しか実装していないので、式解釈としては完全ではない。これで対応できなかったものがあったら考える。

/// <summary>
/// 初期化式を生成する
/// </summary>
/// <param name="argumentValue">初期化値</param>
/// <returns>初期化式</returns>
private CodeExpression CreateCodeExpression(object value)
{
  if (value == null)
    return new CodePrimitiveExpression();

  // InstanceDescriptorを生成し、そこからコードを生成する
  TypeConverter converter = TypeDescriptor.GetConverter(value);
  InstanceDescriptor descriptor = (InstanceDescriptor)converter.ConvertTo(
    value, typeof(InstanceDescriptor));

  CodeExpression[] parameters;
  switch (descriptor.MemberInfo.MemberType)
  {
    case MemberTypes.Constructor:
      parameters = GetParameters(descriptor.Arguments);
      return new CodeObjectCreateExpression(
        descriptor.MemberInfo.DeclaringType,
        parameters);
    case MemberTypes.Field:
      return new CodeFieldReferenceExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name);
    case MemberTypes.Property:
      return new CodePropertyReferenceExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name);
    case MemberTypes.Method:
      parameters = GetParameters(descriptor.Arguments);
      return new CodeMethodInvokeExpression(
        new CodeTypeReferenceExpression(descriptor.MemberInfo.DeclaringType),
        descriptor.MemberInfo.Name,
        parameters);
    default:
      throw new Exception(string.Format("Unknown MemberType: {0}",
        descriptor.MemberInfo.MemberType));
  }
}

private CodeExpression[] GetParameters(ICollection arguments)
{
  CodeExpression[] result = new CodeExpression[arguments.Count];
  int i = 0;
  foreach (object argument in arguments)
  {
    CodeExpression expression;
    if (argument == null)
      expression = new CodePrimitiveExpression(null);
    else if (argument is string || argument.GetType().IsPrimitive)
      // string は IsPrimitive = false だが Primitive式
      expression = new CodePrimitiveExpression(argument);
    else
      expression = CreateCodeExpression(argument);
    result[i++] = expression;
  }
  return result;
}