Using a Delegate to call a constructor

喜你入骨 提交于 2020-01-12 08:01:47

问题


I found this but tried to use it and failed.

How can i create an object using reflections and make it fast by putting it in a delegate?

        DynamicMethod dm = new DynamicMethod("MyCtor", t, new Type[] { });            
        var ctor = t.GetConstructor(new Type[] { });
        ILGenerator ilgen = dm.GetILGenerator();
        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Newobj, ctor);
        ilgen.Emit(OpCodes.Ret);
        var d = (Func<T>)dm.CreateDelegate(t);
        dm.Invoke(null, new object[] { });

Before putting it in a deleage i tried to at least invoke it and when i did above i get the error

An unhandled exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll

Additional information: Exception has been thrown by the target of an invocation.

If i call d() instead i get the exception

An unhandled exception of type 'System.ArgumentException' occurred in mscorlib.dll

Additional information: Type must derive from Delegate.

How do i put a no param constructor into a delegate and call it?


回答1:


If you have access to .NET 3.5 (which your use of Func<T> suggests), you may find Expression easier than ILGenerator:

class Foo { }
static void Main() {
    Func<Foo> func = GetCtor<Foo>(); // cache this somewhere!
    Foo foo = func();
}
static Func<T> GetCtor<T>() {
    Type type = typeof(T);
    Expression body = Expression.New(type);
    return Expression.Lambda<Func<T>>(body).Compile();        
}

Pretty easy to extend that to use a specific constructor, passing arguments, or adding post-constructor property bindings; casts, conversions, etc (see this related answer). If you have a specific scenario, I'll happily add an example.

Note also that you should cache and re-use any such constructors - otherwise you lose the benefit (i.e. don't recreate the delegate per-call).




回答2:


Try this -

Action myCtor = CreateCtor(t, Type.EmptyTypes, typeof(Action));

public static Delegate CreateCtor(Type type, Type[] parameterTypes, Type delegateType, string typeParameterName)
{
    var ctorInfo = type.GetConstructor(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null, parameterTypes, null);
    if (ctorInfo == null)
    {
        string parameterString = string.Empty;
        if(parameterTypes.Length > 0)
        {
            string[] parameterStrings = new string[parameterTypes.Length];
            for(int i = 0; i < parameterTypes.Length; ++i)
            {
                parameterStrings[i] = parameterTypes[i].ToString();
            }
            parameterString = string.Join(",", parameterStrings);
        }
        throw new ArgumentException(string.Format("Type '{0}' does not define .ctor({1}).", type, parameterString), typeParameterName);
    }

    bool isVisible = type.IsVisible && (ctorInfo.IsPublic && !ctorInfo.IsFamilyOrAssembly);

    DynamicMethod dynamicCtor = new DynamicMethod(Guid.NewGuid().ToString("N"), type, parameterTypes, ctorInfo.Module, !isVisible);
    var il = dynamicCtor.GetILGenerator();
    for (int i = 0; i < parameterTypes.Length; ++i)
    {
        switch (i)
        {
            case 0: il.Emit(OpCodes.Ldarg_0); break;
            case 1: il.Emit(OpCodes.Ldarg_1); break;
            case 2: il.Emit(OpCodes.Ldarg_2); break;
            case 3: il.Emit(OpCodes.Ldarg_3); break;
            default: il.Emit(OpCodes.Ldarg, i); break;
        }
    }
    il.Emit(OpCodes.Newobj, ctorInfo);
    il.Emit(OpCodes.Ret);
    return dynamicCtor.CreateDelegate(delegateType);
}



回答3:


There are no arguments to the constructor so you should not load arguments on the stack ilgen.Emit(OpCodes.Ldarg_0):

class Program
{
    static void Main()
    {
        var t = typeof(Program);
        var dm = new DynamicMethod("MyCtor", t, new Type[0], t.Module);
        var ctor = t.GetConstructor(new Type[0]);
        ILGenerator ilgen = dm.GetILGenerator();
        ilgen.Emit(OpCodes.Newobj, ctor);
        ilgen.Emit(OpCodes.Ret);
        var del = (Func<Program>)dm.CreateDelegate(typeof(Func<Program>));
        var instance = del();
        Console.WriteLine(instance);
    }
}



回答4:


Generic method for constructing delegates, calling constructor directly. Automatically searches constructor in given type with signature of given delegate type and constructs delegate of that type. Code here:

/// <summary>
/// Reflective object construction helper.
/// All methods are thread safe.
/// </summary>
public static class Constructor
{
    /// <summary>
    /// Searches an instanceType constructor with delegateType-matching signature and constructs delegate of delegateType creating new instance of instanceType.
    /// Instance is casted to delegateTypes's return type. 
    /// Delegate's return type must be assignable from instanceType.
    /// </summary>
    /// <param name="delegateType">Type of delegate, with constructor-corresponding signature to be constructed.</param>
    /// <param name="instanceType">Type of instance to be constructed.</param>
    /// <returns>Delegate of delegateType wich constructs instance of instanceType by calling corresponding instanceType constructor.</returns>
    public static Delegate Compile(Type delegateType,Type instanceType)
    {
        if (!typeof(Delegate).IsAssignableFrom(delegateType))
        {
            throw new ArgumentException(String.Format("{0} is not a Delegate type.",delegateType.FullName),"delegateType");
        }
        var invoke = delegateType.GetMethod("Invoke");
        var parameterTypes = invoke.GetParameters().Select(pi => pi.ParameterType).ToArray();
        var resultType = invoke.ReturnType;
        if(!resultType.IsAssignableFrom(instanceType))
        {
            throw new ArgumentException(String.Format("Delegate's return type ({0}) is not assignable from {1}.",resultType.FullName,instanceType.FullName));
        }
        var ctor = instanceType.GetConstructor(
            BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, parameterTypes, null);
        if(ctor == null)
        {
            throw new ArgumentException("Can't find constructor with delegate's signature","instanceType");
        }
        var parapeters = parameterTypes.Select(Expression.Parameter).ToArray();

        var newExpression = Expression.Lambda(delegateType,
            Expression.Convert(Expression.New(ctor, parapeters), resultType),
            parapeters);
        var @delegate = newExpression.Compile();
        return @delegate;
    }
    public static TDelegate Compile<TDelegate>(Type instanceType)
    {
        return (TDelegate) (object) Compile(typeof (TDelegate), instanceType);
    }
}

is part of Yappi project's sources. Using it you can construct delegate calling any constructor of given type, including constructor with parameters (except ref and out parameters).

Sample usage:

var newList = Constructor.Compile<Func<int, IList<String>>>(typeof (List<String>));
var list = newList(100);

After construction of delegate, store it somewhere in static dictionary or in static field of class with generic parameter. Don't construct new delegate each time. Use one delegate for constructing multiple instances of given type.



来源:https://stackoverflow.com/questions/2051359/using-a-delegate-to-call-a-constructor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!