CodeDomを使ったコードジェネレーター

ビジネスロジックをもつクラスから、WCFのサービスコントラクトを生成する自作ツールを使っています。リフレクションを使ってメソッド情報を取得し、それを String.Format で整形して出力するというベタな方法で実装した簡単なものです。

でもこの方法だとジェネリックが絡んだときに上手く出力できません。奴らは Type.Name で名前を取得したら < > を [ ] にして返しやがります。しかも、[ の前にジェネリックパラメータの数とか付けちゃってくれます。Dictionary> みたいに、ジェネリックが入れ子になったら、もう地獄。

そこで CodeDom の出番。CodeDom を使えば、ツリーを組み立てるように、ソースコードを生成できます。ジェネリックだって上手く処理してくれます。

サンプルがこちら。ジェバンニが一晩でやってくれました

using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.ServiceModel;
using System.Text;
using Microsoft.CSharp;

namespace CodeDomSample
{
    public static class CodeGenerator
    {
        /// <summary>
        /// インタフェースのコードを生成します。
        /// </summary>
        /// <param name="type">インタフェースを抽出する型</param>
        public static string Generate(Type type)
        {
            // インタフェース定義
            CodeTypeDeclaration codeInterface = new CodeTypeDeclaration("I" + type.Name);
            codeInterface.IsInterface = true;
            // ServiceContract 属性を付けてみる
            codeInterface.CustomAttributes.Add(
                new CodeAttributeDeclaration(
                    new CodeTypeReference(typeof(ServiceContractAttribute))
                )
            );

            // メンバ定義
            var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance);
            foreach (var method in methods)
            {
                // Object のメンバは無視
                if (typeof(object) == method.DeclaringType)
                {
                    continue;
                }

                // メソッド定義
                var codeMethod = new CodeMemberMethod();
                codeMethod.Name = method.Name;
                codeMethod.ReturnType = new CodeTypeReference(method.ReturnType);
                // OperationContract 属性を付けてみる
                codeMethod.CustomAttributes.Add(
                    new CodeAttributeDeclaration(
                        new CodeTypeReference(typeof(OperationContractAttribute))
                    )
                );

                // ジェネリックパラメータ追加
                if (method.IsGenericMethod)
                {
                    foreach (var genericType in method.GetGenericArguments())
                    {
                        codeMethod.TypeParameters.Add(genericType.Name);
                    }
                }

                // 引数追加
                foreach (var parameter in method.GetParameters())
                {
                    var codeParameter = new CodeParameterDeclarationExpression(
                        parameter.ParameterType,
                        parameter.Name);
                    codeMethod.Parameters.Add(codeParameter);
                }

                codeInterface.Members.Add(codeMethod);
            }

            // 名前空間定義の作成
            CodeNamespace ns = new CodeNamespace(type.Namespace);
            ns.Types.Add(codeInterface);

            // 文字列化
            StringBuilder sb = new StringBuilder();
            using (TextWriter writer = new StringWriter(sb))
            {
                CSharpCodeProvider provider = new CSharpCodeProvider();
                provider.GenerateCodeFromNamespace(ns, writer, new CodeGeneratorOptions()
                {
                    BracingStyle = "C",
                    IndentString = "    ",
                    BlankLinesBetweenMembers = true,
                });
                writer.Flush();
            }
            return sb.ToString();
        }
    }

    public class Foo
    {
        public string Greet(string name)
        {
            return string.Format("Hello, {0}.", name);
        }

        public void Echo(string name)
        {
            Console.WriteLine(name);
        }

        public Dictionary<string, List<int>> GetDictiionary()
        {
            return new Dictionary<string, List<int>>();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine(CodeGenerator.Generate(typeof(Foo)));
            Console.ReadLine();
        }
    }
}

f:id:griefworker:20100730171058j:image

CodeDome はとっつきにくいですけど、慣れれば出力内容をコードにべた書きするよりも楽にコードジェネレーターが作れる、気がします。多分。メイビー。