2009年12月04日

メソッドの型引数を使った引数

今回のネタは Enumerableの拡張メソッドをGetMethodしたい から。

こういう拡張メソッドはどうでしょうか。Tuple<T1, T2> クラスはサンプルのために作った型引数を二つ取る適当なクラスなので、KeyValuePair<TKey, TValue> などで置き換えできます。ちなみに拡張メソッドにせず一部の Enumerale のメソッドを手で書けば .NET 2.0 以上で動くはずです。


//  使い方
public class Sample {
  public void Test<T>(List<T> arg) { }
  public void Test<T1, T2>(Tuple<T1, T2> arg1,
                           Tuple<Tuple<Int32, Int32>,
                                 Tuple<Int32, T2>> arg2) { }

  static void Main() {
    var sample = typeof(Sample);

    //  Test<T> のメソッドを取得
    //  メソッド型引数を使った引数型を構築する
    //  型引数の指定はインデックス(0)または名前("T")で
    var openType = new OpenTypeBuilder(typeof(List<>)).Add(0).Build();
    var method1 = sample.GetMethod("Test", openType);
    Console.WriteLine(method1);

    //  Test<T1, T2> のメソッドを取得
    //  メソッド型引数を使った引数型の構築が面倒なのは避けられない
    Type tuple2 = typeof(Tuple<,>), int32 = typeof(Int32);
    //  一つ目の引数 Tuple<T1, T2> の型を構築
    var t1t2 = new OpenTypeBuilder(tuple2).Add("T1").Add("T2").Build();
    //  二つ目の引数型はまずその構成要素の Tuple<Int32, Int32> などから先に構築
    var intInt = typeof(Tuple<Int32, Int32>); //  こちらは静的に Type を作成可能
    var intT2 = new OpenTypeBuilder(tuple2).Add(int32).Add("T2").Build();
    //  構成要素二つをもとに二つ目の引数型を構築
    var tupleTuple = new OpenTypeBuilder(tuple2).Add(intInt).Add(intT2).Build();
    //  二つの引数型を使ってメソッドを取得
    var method2 = sample.GetMethod("Test", t1t2, tupleTuple);
    Console.WriteLine(method2);
  }
}


public static class TypeExtensions {
  //  特定の名前を持つメソッドの MethodInfo 配列を返す
  public static MethodInfo[] GetMethods(this Type type, string name) {
    return type.GetMethods().Where(method => method.Name == name).ToArray();
  }
  //  特定の名前と flags に合致するメソッドの MethodInfo 配列を返す
  public static MethodInfo[] GetMethods(this Type type, string name,
                                        BindingFlags flags) {
    return type.GetMethods(flags).Where(method => method.Name == name).ToArray();
  }
  //  特定の名前と引数型を持つ public メソッドの MethodInfo を返す
  public static MethodInfo GetMethod(this Type type, string name,
                                     params GenericType[] argTypes) {
    return GetMethod(type, name,
                     BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static,
                     argTypes);
  }
  //  特定の名前と引数型を持つメソッドの MethodInfo を返す
  public static MethodInfo GetMethod(this Type type, string name, BindingFlags flags,
                                     params GenericType[] argTypes) {
    MethodInfo retval = null;
    //  まず該当する名前のメソッドを列挙する
    foreach (var method in GetMethods(type, name)) {
      //  MethodInfo からそのメソッドの型引数の型の配列が手に入る
      var methodTypes = method.GetGenericArguments();
      var parameters = method.GetParameters().Select(p => p.ParameterType).ToArray();
      //  引数の長さが違ったら絶対別物
      if (parameters.Length != argTypes.Length) continue;
      bool isMatch = true;
      for (int i = 0; i < parameters.Length; ++i) {
        if (parameters[i] != argTypes[i].GetType(methodTypes)) {
          isMatch = false;
          break;
        }
      }
      if (isMatch) {
        if (retval != null)
          throw new AmbiguousMatchException("あいまいな一致が見つかりました。");
        retval = method;
      }
    }
    return retval;
  }
}
//  3 タイプの型表現をまとめて扱うための基底抽象クラス
public abstract class GenericType {
  //  メソッドの型引数から Type オブジェクトを取得・生成する抽象メソッド
  public abstract Type GetType(Type[] typeArgumentTypes);
}
//  静的に Type を作成できる型
public sealed class ClosedType : GenericType {
  public Type Type { get; private set; }
  //  コンストラクタ引数が正当かどうかのチェックは省略
  //  type が閉じていること(Type.ContainsGenericParameter)
  public ClosedType(Type type) { this.Type = type; }
  //  既に型は作られているのでそれをそのまま返すだけ
  public override Type GetType(Type[] typeArgumentTypes) {
    return this.Type;
  }
}
//  メソッド型引数の型 Test<T1, T2>(T1 arg1, T2 arg2) の T1・T2
//  メソッド型引数内のインデックス、または型引数の名前で識別する
public sealed class TypeArgumentType : GenericType {
  //  メソッド型引数内の 0 オリジンのインデックス。T1 なら 0、T2 なら 1
  public int TypeArgumentIndex { get; private set; }
  //  メソッド型引数の名前。T1 なら "T1"。Case-sensitive。
  public string TypeArgumentName { get; private set; }
  //  型引数内のインデックスで識別する用のコンストラクタ
  public TypeArgumentType(int index) { this.TypeArgumentIndex = index; }
  //  型引数の名前で識別する用のコンストラクタ
  public TypeArgumentType(string name) {
    this.TypeArgumentName = name;
    this.TypeArgumentIndex = -1;
  }
  //  メソッドの型引数型配列内のうち、自分に合致するものを返す
  public override Type GetType(Type[] typeArgumentTypes) {
    if (this.TypeArgumentIndex < 0) {
      //  名前で識別する場合、Type.Name が合致するかどうかで判断する
      return typeArgumentTypes.FirstOrDefault(
          type => type.Name == this.TypeArgumentName);
    }
    else {
      //  インデックスで識別する場合、インデックスがオーバーする可能性がある
      if (this.TypeArgumentIndex >= typeArgumentTypes.Length)
        return null;
      return typeArgumentTypes[this.TypeArgumentIndex];
    }
  }
}
//  メソッド型引数の型を型引数に持つジェネリック型の型
//  Test<T1, T2>(Tuple<T1, Tuple<Int32, T2>> arg) の arg の型
public sealed class OpenType : GenericType {
  //  型引数が未指定の開いたジェネリック型。typeof(Tuple<,>)
  public Type BaseType { get; private set; }
  //  型引数のリスト。T1 を指す TypeArgumentType や
  //  Tuple<Int32, T2> を指す OpenType などの配列
  public GenericType[] TypeArguments { get; private set; }
  //  コンストラクタ引数が正当かどうかのチェックは省略
  //  baseType は一つ以上型引数を持ち全ての型引数が未指定であること
  //  typeArguments は baseType の型引数と同じ要素数であること など
  public OpenType(Type baseType, GenericType[] typeArguments) {
    this.BaseType = baseType;
    this.TypeArguments = typeArguments;
  }
  //  メソッドの型引数型が明らかになったので
  //  それを使ってジェネリック型を再帰的に構築できる
  //  T1 を指す TypeArgumentType なら "T1" や 0 から T1 型を取得できる
  //  Tuple<Int32, T2> を指す OpenType は TypeArguments に
  //    Int32 の ClosedType 及び T2 の TypeArgumentType を持っていて、
  //    その二つはそれぞれ GetType で Int32 型と T2 型を返す。
  //    BaseType の Tuple<,> にこの二つの Type を MakeGenericType することで
  //    Tuple<Int32, T2> を指す Type が作成できる。
  //  最後に Tuple<,> 及び T1、Tuple<Int32, T2> から
  //  Tuple<T1, Tuple<Int32, T2>> の Type が MakeGenericType で作成される。
  public override Type GetType(Type[] typeArgumentTypes) {
    var args = new List<Type>();
    foreach (var arg in this.TypeArguments) {
      var argType = arg.GetType(typeArgumentTypes);
      if (argType == null)
        return null;
      args.Add(argType);
    }
    return this.BaseType.MakeGenericType(args.ToArray());
  }
}
//  直接 OpenType を作るのは面倒なのでヘルパクラスを用意
public class OpenTypeBuilder {
  //  OpenType の基となる型を指定してインスタンスを生成 引数の正当性は省略
  //  一つ以上型引数を持ち全ての型引数が未指定であること
  public OpenTypeBuilder(Type baseType) {
    this.baseType = baseType;
  }
  //  メソッド型引数のインデックスを指定して型引数型の型引数を追加
  //  自分自身を返すので builder.Add(0).Add("T1").Build() という使い方が可能
  public OpenTypeBuilder Add(int index) {
    this.typeArguments.Add(new TypeArgumentType(index));
    return this;
  }
  //  メソッド型引数の名前を指定して型引数型の型引数を追加
  public OpenTypeBuilder Add(string name) {
    this.typeArguments.Add(new TypeArgumentType(name));
    return this;
  }
  //  Type を指定して静的な型の型引数を追加
  public OpenTypeBuilder Add(Type type) {
    this.typeArguments.Add(new ClosedType(type));
    return this;
  }
  //  OpenType を指定してメソッド型引数の型を使用するジェネリック型の型引数を追加
  public OpenTypeBuilder Add(OpenType type) {
    this.typeArguments.Add(type);
    return this;
  }
  //  OpenType を生成する 追加した型引数の過不足などのチェックは省略
  public OpenType Build() {
    return new OpenType(this.baseType, this.typeArguments.ToArray());
  }
  private Type baseType;
  private List<GenericType> typeArguments = new List<GenericType>();
}
posted by Hongliang at 20:29| Comment(3) | TrackBack(0) | C# | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
ぬるり。: メソッドの型引数を使った引数
Posted by gucci バッグ アウトレット at 2013年07月21日 15:38
コルセット
Posted by 靴 クロックス at 2013年08月06日 03:23
財布 バッグ 販売 http://www.yubqm.com/
Posted by バッグ 販売 at 2013年08月13日 22:03
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。
この記事へのトラックバックURL
http://blog.seesaa.jp/tb/134709535

この記事へのトラックバック

ここ(hongliang.seesaa.net)で公開しているものについて、利用は自由に行って頂いて構いません。改変、再頒布もお好きになさって下さい。利用に対しこちらが何かを要求することはありません。

ただし、公開するものを使用、または参考したことによって何らかの損害等が生じた場合でも、私はいかなる責任も負いません。

あ、こんなのに使ったってコメントを頂ければ嬉しいです。

×

この広告は1年以上新しい記事の投稿がないブログに表示されております。