#前書き
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でなくても呼び出せてしまう
引数の型や数を間違えてもコンパイルエラーが出ない