FromEventが面倒なので自動生成させてみた2

C# Reactive Extensionsネタ

前の日記で書いた自動生成ロジックが
継承を意識していなかったため基底クラスの同じメソッドを生成していたので、
クラス毎にメソッドを生成するようにしてみた。

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Windows.Forms" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Reflection" #>
<#@ import namespace="System.Windows.Forms" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>

using System;
using System.Collections.Generic;
using System.Linq;

internal static class ControlEventExtensions
{
<#  // ここにGenerateCode(params Type[] types)に任意の型を入れて書く
    // 例 GenerateCode(typeof(PictureBox),typeof(Panel))
#>
<#= GenerateCode(typeof(PictureBox),typeof(Panel)) #>
}


<#+
    /// <summary>
    /// メソッドテンプレート
    /// </summary>
    private const string MethodCodeTemplate = "\r\n" +
    "    public static IObservable<IEvent<{0}>> Get{1}(this {2} that)\r\n" +
    "    {{\r\n" +
    "        return Observable.FromEvent<{0}>(that, \"{1}\");\r\n" +
    "    }}\r\n";

    /// <summary>
    /// 与えられた型リストから継承関係を意識してコードを生成する。
    /// </summary>
    /// <param name="types">自動生成させる型</param>
    /// <returns></returns>
    public static string GenerateCode(params Type[] types)
    {
        // 指定されたすべての型に対して、現在の型と継承元の型をリストにする。
        var typeList = types
            // 型を展開する
            .SelectMany(t => GetParentType(t))
            // 同一の型は1つのみにする
            .Distinct()
            // ソートして順序を固定化しておく
            .OrderBy(t => t.FullName)
            // リストの取得
            .ToList();

        var sb = new StringBuilder(1000);
        foreach (var type in typeList) {
            sb.Append(GenerateCode(type));
        }
        return sb.ToString();
    }

    /// <summary>
    /// 型の継承ツリーをたどる
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static IEnumerable<Type> GetParentType(Type type)
    {
        var prevType = type;
        while (prevType != typeof(object)) {
            yield return prevType;
            prevType = prevType.BaseType;
        }
    }

    /// <summary>
    /// コード生成
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    public static string GenerateCode(Type type)
    {
        // ベースの型を取得
        var events = type.GetEvents(
            BindingFlags.Public |
            BindingFlags.InvokeMethod |
            BindingFlags.DeclaredOnly |
            BindingFlags.Instance
            );
        // イベントのリストからイベント名とイベントの型のリストを取得する
        var list = events.Select(ev => new { Handler = ev.EventHandlerType, Name = ev.Name })
                            .Select(ev => new {
                                HandlerType = ev.Handler,
                                Parameters = GetDelegateParameterTypes(ev.Handler),
                                ev.Name
                            })
                            .ToList();
        var list2 = list
            // 引数2以外のやつは存在するのかよくわからないのでとりあえず読み込まない
            .Where(v => v.Parameters.Length == 2)
            .Select(v => string.Format(MethodCodeTemplate,
                                        v.Parameters[1].FullName,
                                        v.Name,
                                        type.FullName));


        return string.Join("", list2.ToArray());
    }

    /// <summary>
    /// デリゲートの型からパラメータの型を取得する
    /// </summary>
    /// <see>http://msdn.microsoft.com/ja-jp/library/ms228976(VS.95).aspx</see>
    /// <param name="d"></param>
    /// <returns></returns>
    private static Type[] GetDelegateParameterTypes(Type d)
    {
        if (d.BaseType != typeof(MulticastDelegate)) {
            throw new InvalidOperationException("Not a delegate.");
        }

        MethodInfo invoke = d.GetMethod("Invoke");
        if (invoke == null) {
            throw new InvalidOperationException("Not a delegate.");
        }

        ParameterInfo[] parameters = invoke.GetParameters();
        Type[] typeParameters = parameters.Select(p => p.ParameterType).ToArray();

        return typeParameters;
    }
    
#>

で、生成されたコードがこれ

using System;
using System.Collections.Generic;
using System.Linq;

internal static class ControlEventExtensions
{

    public static IObservable<IEvent<System.EventArgs>> GetDisposed(this System.ComponentModel.Component that)
    {
        return Observable.FromEvent<System.EventArgs>(that, "Disposed");
    }

    public static IObservable<IEvent<System.EventArgs>> GetAutoSizeChanged(this System.Windows.Forms.Control that)
    {
        return Observable.FromEvent<System.EventArgs>(that, "AutoSizeChanged");
    }

    public static IObservable<IEvent<System.EventArgs>> GetBackColorChanged(this System.Windows.Forms.Control that)
    {
        return Observable.FromEvent<System.EventArgs>(that, "BackColorChanged");
    }

    // とても長いので省略

    public static IObservable<IEvent<System.Windows.Forms.KeyPressEventArgs>> GetKeyPress(this System.Windows.Forms.PictureBox that)
    {
        return Observable.FromEvent<System.Windows.Forms.KeyPressEventArgs>(that, "KeyPress");
    }

    public static IObservable<IEvent<System.EventArgs>> GetLeave(this System.Windows.Forms.PictureBox that)
    {
        return Observable.FromEvent<System.EventArgs>(that, "Leave");
    }

    public static IObservable<IEvent<System.Windows.Forms.ScrollEventArgs>> GetScroll(this System.Windows.Forms.ScrollableControl that)
    {
        return Observable.FromEvent<System.Windows.Forms.ScrollEventArgs>(that, "Scroll");
    }

}

これでComponent.DisposedやControlのメソッド群が複数回生成されなくなったので、
精神衛生上よろしくなった気がします。

前回挙げた課題の

  • 継承を意識していないため、クラスを複数個登録すると同じようなメソッドが大量に生成される(Controlで定義している奴とか)。

    解決

  • GenerateCode()/GenerateCode(typeof(Panel))/GenerateCode("Panel")のどれががいいのかよくわからない

    typeofに決定

  • メソッドテンプレートあたりがスマートじゃない気がする

    未解決

  • 適切なドキュメントコメントがほしい(無理っぽい?)

    ドキュメントコメントはメタデータとして存在しないので無理