3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

「C#」ジェネリック関数で引数ありコンストラクタを呼ぶコード

Posted at

#前書き
C# 7.3でジェネリックを使って引数のあるコンストラクターを呼ぶ
コードを考えてみました

#コード

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq.Expressions;

namespace Sample {
	static class Test {
		static void Main() {
			Console.WriteLine("\n直接、引数ありコンストラクタを呼ぶ");
			Console.WriteLine(ObjA.New(0, 1, 2));
			Console.WriteLine(ObjB.New(3, 4, 5));

			Console.WriteLine("\nジェネリックメソッドで引数ありコンストラクタを呼ぶ");
			Console.WriteLine(CreateObjBase<ObjA>(0, 1, 2));
			Console.WriteLine(CreateObjBase<ObjB>(3, 4, 5));
		}

		//引数ありコンストラクターを呼ばせるジェネリックで関数
		static T CreateObjBase<T>(int id1, int id2, int id3) where T : ObjBase {
			var maker = NewObjectHelper<T>.CreateMethod<Func<int, int, int, T>>();

			return maker(id1, id2, id3);
		}
	}

	//TInstanceに戻り値の型を指定
	static class NewObjectHelper<TInstance> {
		//使うFunc型から引数の型だけ抜き出す
		static Type[] CreateParameterType<TFunc>() where TFunc : Delegate {
			//Type.GetGenericArguments()で
			//ジェネリック型に指定された型を全て取り出せる
			var method_use_types = new List<Type>(typeof(TFunc).GetGenericArguments());
			
			//使うFunc型の戻り値の型だけ除外
			var deleteIndex = method_use_types.FindIndex((i) => i == typeof(TInstance));
			method_use_types.RemoveAt(deleteIndex);

			return method_use_types.ToArray();
		}
		//Type配列から式木用の配列を作成
		static List<ParameterExpression> CreateParamsListFromTypes(Type[] types) {
			var paramList = new List<ParameterExpression>(types.Length);

			foreach (var i in types) paramList.Add(Expression.Parameter(i));

			return paramList;
		}

		//式木を使ってコンストラクタを呼ぶ関数を作成
		public static TFunc CreateMethod<TFunc>() where TFunc : Delegate {
			var argsType = CreateParameterType<TFunc>();
			var paramList = CreateParamsListFromTypes(argsType);
			var flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

			var constructor = typeof(TInstance).GetConstructor(flag,
				Type.DefaultBinder,
				argsType,
				null
			);

			return Expression.Lambda<TFunc>(
				Expression.New(
					constructor, paramList),
				paramList).Compile();
		}
		//式木を使ってコンストラクタを呼ぶ関数を作成
		//
		//引数のtypeValueで戻り値の型推論をさせる
		public static TFunc CreateMethod<TFunc>(TFunc typeValue) where TFunc : Delegate {
			var argsType = CreateParameterType<TFunc>();
			var paramList = CreateParamsListFromTypes(argsType);
			var flag = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;

			var constructor = typeof(TInstance).GetConstructor(flag,
				Type.DefaultBinder,
				argsType,
				null
			);

			return Expression.Lambda<TFunc>(
				Expression.New(
					constructor, paramList),
				paramList).Compile();
		}
	}

	//テスト用クラス
	abstract class ObjBase {
		public int Id1;
		public int Id2;
		public int Id3;

		public override string ToString() {
			return  "[" + Id1 + " : " + Id2 + " : " + Id3 + "]";
		}
	}
	sealed class ObjA : ObjBase {
		public static Func<int, int, int, ObjA> New { get; } = NewObjectHelper<ObjA>.CreateMethod(New);

		ObjA(int id1, int id2, int id3) {
			Id1 = id1;
			Id2 = id2;
			Id3 = id3;
		}

		public override string ToString() {
			return nameof(ObjA) + " " + base.ToString();
		}
	}
	sealed class ObjB : ObjBase {
		public static Func<int, int, int, ObjB> New { get; } = NewObjectHelper<ObjB>.CreateMethod(New);

		ObjB(int id1, int id2, int id3) {
			Id1 = id1;
			Id2 = id2;
			Id3 = id3;
		}

		public override string ToString() {
			return nameof(ObjB) + " " + base.ToString();
		}
	}
}

#注意点
コンストラクタがpublicでなくても呼び出せてしまう
引数の型や数を間違えてもコンパイルエラーが出ない

3
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?