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

前の日記で自動生成できないかなぁと言っていたものが
T4 Template使えばできそうだったのでやってみた。
インテリセンスの効かないコーディングはとてもストレスが溜まるものでした。

<#@ template language="C#v3.5" #>
<#@ 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" #>

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

internal static class ControlEventExtensions
{
	<# // ここにGenerateCode<T>()でTに任意の型を入れて書く #>
	<#= GenerateCode<PictureBox>() #>	
	<#= GenerateCode<Panel>() #>
}


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

    /// <summary>
    /// コード生成
    /// </summary>
    /// <typeparam name="T">生成対象のクラス</typeparam>
    /// <returns></returns>
    public static string GenerateCode<T>()
    {
		var padding = "        ";
        // ベースの型を取得
        var type = typeof(T);
        var events = type.GetEvents();
        // イベントのリストからイベント名とイベントの型のリストを取得する
        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)
            .Where(v => v.Parameters[1].IsSubclassOf(typeof(EventArgs)) || v.Parameters[1]==typeof(EventArgs))
            .Select(v => string.Format(MethodCodeTemplate,
                                        v.Parameters[1].FullName,
                                        v.Name,
                                        type.FullName,
                                        padding));

        return string.Join("\r\n", 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>> GetCausesValidationChanged(this System.Windows.Forms.PictureBox that)
        {
            return Observable.FromEvent<System.EventArgs>(that, "CausesValidationChanged");
        }

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

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

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

        public static IObservable<IEvent<System.ComponentModel.AsyncCompletedEventArgs>> GetLoadCompleted(this System.Windows.Forms.PictureBox that)
        {
            return Observable.FromEvent<System.ComponentModel.AsyncCompletedEventArgs>(that, "LoadCompleted");
        }
        // とても長いので途中省略
}

やっていることは単純で

  1. GenerateCodeに型引数を渡す
  2. リフレクションでeventのデリゲートを取得
  3. デリゲートからパラメータを取得
  4. 2引数かつ、第2引数がEventArgsまたはEventArgsから派生したメソッドのリストを取得
  5. メソッドのテンプレートに必要な文字を埋め込んで出力

今後の課題

  • 継承を意識していないため、クラスを複数個登録すると同じようなメソッドが大量に生成される(Controlで定義している奴とか)。
  • GenerateCode()/GenerateCode(typeof(Panel))/GenerateCode("Panel")のどれががいいのかよくわからない
  • メソッドテンプレートあたりがスマートじゃない気がする
  • 適切なドキュメントコメントがほしい(無理っぽい?)

Linq to Eventはこれでかなり快適にインテリセンスが効くようになってイイ感じです。
しかし、VS2008は発売日に買ったのにT4 Templateを知ったのが今月というのは…
なんともったいないことか。