コンストラクターへの (オープン) デリゲートを作成するメソッドを作成しました。任意の数の引数と変数 (ref や out など) を持つ任意の (静的またはインスタンス) コンストラクターで使用できます。デリゲートが void を返す場合、構築された型のインスタンスが最初のパラメーターとして期待されます。デリゲートが構築する型を返す場合、新しいインスタンスが作成されます。
static public T CreateDelegate<T>(this ConstructorInfo constructor)
{
// Validate if the constructor is not null.
if (constructor == null)
throw new ArgumentNullException("constructor");
// Validate if T is a delegate.
Type delegateType = typeof(T);
if (!typeof(Delegate).IsAssignableFrom(delegateType))
throw new ArgumentException("Generic argument T must be a delegate.");
// Get alle needed information.
MethodInfo invoke = delegateType.GetMethod("Invoke");
ParameterInfo[] constructorParams = constructor.GetParameters();
ParameterInfo[] delegateParams = invoke.GetParameters();
// What kind of delegate is going to be created (open, creational, static).
bool isOpen = false;
OpCode opCode = OpCodes.Newobj;
int parameterOffset = 0;
if (constructor.IsStatic) // Open delegate.
{
opCode = OpCodes.Call;
if (invoke.ReturnType != typeof(void))
throw new ArgumentException("Delegate to static constructor cannot have a return type.");
if (delegateParams.Length != 0)
throw new ArgumentException("Delegate to static constructor cannot have any parameters.");
}
else if (invoke.ReturnType == typeof(void)) // Open delegate.
{
opCode = OpCodes.Call;
isOpen = true;
parameterOffset = 1;
if ((delegateParams.Length == 0) || (delegateParams[0].ParameterType != constructor.DeclaringType))
throw new ArgumentException("An open delegate must have a first argument of the same type as the type that is being constructed.");
}
else // Creational delegate.
{
if (invoke.ReturnType != constructor.DeclaringType)
throw new ArgumentException("Return type of delegate must be equal to the type that is being constructed.");
}
// Validate the parameters (if any).
if (constructorParams.Length + parameterOffset != delegateParams.Length)
throw new ArgumentException(isOpen
? "The number of parameters of the delegate (the argument for the instance excluded) must be the same as the number of parameters of the constructor."
: "The number of parameters of the delegate must be the same as the number of parameters of the constructor.");
for (int i = 0; i < constructorParams.Length; i++)
{
ParameterInfo constructorParam = constructorParams[i];
ParameterInfo delegateParam = delegateParams[i + parameterOffset];
if (constructorParam.ParameterType != delegateParam.ParameterType)
throw new ArgumentException("Arguments of constructor and delegate do not match.");
}
// Create the dynamic method.
DynamicMethod method = new DynamicMethod(
"",
invoke.ReturnType,
delegateParams.Select(p => p.ParameterType).ToArray(),
constructor.DeclaringType.Module,
true);
// Create the IL.
ILGenerator gen = method.GetILGenerator();
for (int i = 0; i < delegateParams.Length; i++)
gen.Emit(OpCodes.Ldarg, i);
gen.Emit(opCode, constructor);
gen.Emit(OpCodes.Ret);
// Return the delegate :)
return (T)(object)method.CreateDelegate(delegateType);
}
デリゲートを作成するには、次を使用します。
public class MyObject
{
public MyObject(int anyValue)
{
...
}
}
Action<MyObject, int> c = typeof(MyObject)
.GetConstructor(new [] { typeof(int) })
.CreateDelegate<Action<MyObject, int>>();
MyObject myObject = new MyObject(1;
c(myObject, 2);
これはすべて、追加の関数を追加することで少し短くすることができます:
static public T CreateConstructorDelegate<T>(this Type type)
{
// Validate if the constructor is not null.
if (type == null)
throw new ArgumentNullException("type");
// Validate if T is a delegate.
Type delegateType = typeof(T);
if (!typeof(Delegate).IsAssignableFrom(delegateType))
throw new ArgumentException("Generic argument T must be a delegate.");
// Validate the delegate return type
MethodInfo invoke = delegateType.GetMethod("Invoke");
int parameterOffset = 0;
BindingFlags binding = BindingFlags.Public | BindingFlags.Instance;
if (invoke.ReturnType == typeof(void))
{
if (invoke.GetParameters().Length == 0)
binding = BindingFlags.NonPublic | BindingFlags.Static; // For static constructors.
else
parameterOffset = 1; // For open delegates.
}
// Validate the signatures
ParameterInfo[] delegateParams = invoke.GetParameters();
ConstructorInfo constructor = type.GetConstructor(binding, null, delegateParams.Skip(parameterOffset).Select(p => p.ParameterType).ToArray(), null);
if (constructor == null)
throw new ArgumentException("Constructor with specified parameters cannot be found.");
return constructor.CreateDelegate<T>();
}
デリゲートを作成する呼び出しは次のようになります。
Action<MyObject, int> c = typeof(MyObject)
.CreateConstructorDelegate<Action<MyObject, int>>();
// Call constructor.
MyObject myObject = new MyObject(1);
// Call constructor again on same object.
c(myObject, 2);
警告! これらのメソッドを呼び出すたびに、小さなコードが作成されます。同じコンストラクターに対してこれらの関数を何度も呼び出す場合は、キャッシュについて考えてください。