How to pass ctor args in Activator.CreateInstance or use IL?

后端 未结 8 1739
野的像风
野的像风 2020-12-07 16:06

I need a performance enhanced Activator.CreateInstance() and came across this article by Miron Abramson that uses a factory to create the instance in IL and then cache it. (

相关标签:
8条回答
  • 2020-12-07 16:30

    I typically use the following method to completely avoid Activator.CreateInstance() in generic classes.

    I require a Func<T> delegate to be passed to it it the constructor. The class will call this if it needs to instantiate a new instance of T. This will make instantiation as quick as in a non-generic class and is more simple and elegant IMO than a complex class that emits IL. Another upside (VS New T()) when using this method is that you can instantiate a class with a parametrized constructor.

    Edit: I have updated the the code with a parametrized constructor example per BillW's request.

    class GenericClass<T>
    {
        public GenericClass(Func<T> classFactory)
        {
            this.ClassFactory = classFactory;
        }
        Func<T> ClassFactory;
    
        public void CreateNewInstDemo()
        {
            //T NewObject = New T(); // <- calls the slow Activator.CreateInstance() in IL
            T NewObject = ClassFactory(); // Calls the quick IL mnemonic 'newobj'
        }
    }
    class GenericClassParamsDemo<T>
    {
        public GenericClassParamsDemo(Func<int, T> classFactory)
        {
            this.ClassFactory = classFactory;
        }
        Func<int, T> ClassFactory;
    
        public void CreateNewInstDemo()
        {
            T NewObject = ClassFactory(5); // Calls the quick IL mnemonic 'newobj'
        }
    }
    class ClassToCreate
    {
        public int Number { get; set; }
        public ClassToCreate()
        {
            // Default constructor
        }
        public ClassToCreate(int number)
        {
            // Constructor With Parameter
            this.Number = number;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            GenericClass<ClassToCreate> gc =
            new GenericClass<ClassToCreate>(() => { return new ClassToCreate(); });
            // Call method which uses delegate to create a new instance
            gc.CreateNewInstDemo();
    
            GenericClassParamsDemo<ClassToCreate> gcParams =
            new GenericClassParamsDemo<ClassToCreate>((number) => { return new ClassToCreate(number); });
             // Call method which uses delegate to create a new instance with params
            gcParams.CreateNewInstDemo();
        }
    }
    
    0 讨论(0)
  • 2020-12-07 16:35

    I wrote this a while ago. It's using the .NET 3.5 Linq Expression trees rather than emitting IL, which is almost as fast, and more maintainable. It can take up to 4 constructor arguments.

    Using any constructor arguments like you want to do might be a bit slower however due to looking up the constructor based on the argument types, but it's still much faster than with reflection. Also with IL emission there would have to be some lookup I think.

    You have to specify the exact type which you want to construct as it's no IOC/DI container. Maybe you can extend and adapt it to your needs.

    // usage:
    Cat myCat = Instantiator<Cat>.New("furry", /* isCute*/ true);
    
    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    
    static public class Instantiator<TInstance>
    {
        static Instantiator()
        {
            Debug.Assert(typeof(TInstance).IsValueType || (typeof(TInstance).IsClass && !typeof(TInstance).IsAbstract),
                    String.Concat("The type ", typeof(TInstance).Name, " is not constructable."));
        }
    
        static public TInstance New()
        {
            return InstantiatorImpl.CtorFunc();
        }
    
        static public TInstance New<TA>(TA valueA)
        {
            return InstantiatorImpl<TA>.CtorFunc(valueA);
        }
    
        static public TInstance New<TA, TB>(TA valueA, TB valueB)
        {
            return InstantiatorImpl<TA, TB>.CtorFunc(valueA, valueB);
        }
    
        static public TInstance New<TA, TB, TC>(TA valueA, TB valueB, TC valueC)
        {
            return InstantiatorImpl<TA, TB, TC>.CtorFunc(valueA, valueB, valueC);
        }
    
        static public TInstance New<TA, TB, TC, TD>(TA valueA, TB valueB, TC valueC, TD valueD)
        {
            return InstantiatorImpl<TA, TB, TC, TD>.CtorFunc(valueA, valueB, valueC, valueD);
        }
    
        static private Expression<TDelegate> CreateLambdaExpression<TDelegate>(params Type[] argTypes)
        {
            Debug.Assert(argTypes != null);
    
            ParameterExpression[] paramExpressions = new ParameterExpression[argTypes.Length];
    
            for (int i = 0; i < paramExpressions.Length; i++)
            {
                paramExpressions[i] = Expression.Parameter(argTypes[i], String.Concat("arg", i));
            }
    
            ConstructorInfo ctorInfo = typeof(TInstance).GetConstructor(argTypes);
            if (ctorInfo == null)
            {
                throw new ArgumentException(String.Concat("The type ", typeof(TInstance).Name, " has no constructor with the argument type(s) ", String.Join(", ", argTypes.Select(t => t.Name).ToArray()), "."),
                        "argTypes");
            }
    
            return Expression.Lambda<TDelegate>(Expression.New(ctorInfo, paramExpressions), paramExpressions);
        }
    
        static private class InstantiatorImpl
        {
            static public readonly Func<TInstance> CtorFunc = Expression.Lambda<Func<TInstance>>(Expression.New(typeof(TInstance))).Compile();
        }
    
        static private class InstantiatorImpl<TA>
        {
            static public readonly Func<TA, TInstance> CtorFunc = Instantiator<TInstance>.CreateLambdaExpression<Func<TA, TInstance>>(typeof(TA)).Compile();
        }
    
        static private class InstantiatorImpl<TA, TB>
        {
            static public readonly Func<TA, TB, TInstance> CtorFunc = Instantiator<TInstance>.CreateLambdaExpression<Func<TA, TB, TInstance>>(typeof(TA), typeof(TB)).Compile();
        }
    
        static private class InstantiatorImpl<TA, TB, TC>
        {
            static public readonly Func<TA, TB, TC, TInstance> CtorFunc = Instantiator<TInstance>.CreateLambdaExpression<Func<TA, TB, TC, TInstance>>(typeof(TA), typeof(TB), typeof(TC)).Compile();
        }
    
        static private class InstantiatorImpl<TA, TB, TC, TD>
        {
            static public readonly Func<TA, TB, TC, TD, TInstance> CtorFunc = Instantiator<TInstance>.CreateLambdaExpression<Func<TA, TB, TC, TD, TInstance>>(typeof(TA), typeof(TB), typeof(TC), typeof(TD)).Compile();
        }
    }
    

    Have teh funz with it! :->

    0 讨论(0)
  • 2020-12-07 16:41

    I had no idea that new T() was slow in a generic class. My problem is really something else though - if I knew what T was at compile time I'd be fine but I want a quick way to instantiate a class specified by configuration information (i.e. strings holding assembly / class names) at runtime. I use a cache of Type objects to load the assembly and locate the type in it only once, so the last hurdle is to quickly create instances, which was the subject of my previous post.

    Anyhow, following from my finding that .NET 4.0 is quicker at this sort of thing I tested with a version of your example, calling each CreateNewInstxxx method 1,000,000 times. Timings in milliseconds:

    class GenericClass<T> where T : new()
    {
        Func<T> ClassFactory;    
        public GenericClass(Func<T> classFactory)    
        {        
            this.ClassFactory = classFactory;    
        }    
        public T CreateNewInstQuick()    
        {        
            return ClassFactory(); // Calls the quick IL mnemonic 'newobj'   
        }
        public T CreateNewInstStd()
        {
            return new T(); // <- calls the slow Activator.CreateInstance() in IL
        }
    }
    
    .NET 3.5
    CreateNewInstQuick: 35
    CreateNewInstStd: 1298
    
    .NET 4.0
    CreateNewInstQuick: 29
    CreateNewInstStd: 165
    

    So yes, .NET 4.0 is much faster than previously here too. The code generated by the compiler for the CreateNewInstStd() method looks like this in both cases so it seems the speedup is down to the improved Activator.CreateInstance<T>() method:

    public T CreateNewInstStd()
    {
        T t1 = default(T);
        if (t1 != null)
        {
            T t2 = default(T);
            return t2;
        }
        return Activator.CreateInstance<T>();
    }
    

    Edit: To add that performance is roughly the same when the GenericClass is constrained to reference types via this:

    class GenericClass<T> where T :  class, new()
    

    and in that case the compiler-generated code is then just this:

    public T CreateNewInstStd()
    {
        return Activator.CreateInstance<T>();
    }
    
    0 讨论(0)
  • 2020-12-07 16:41

    I think the first thing you need to change is the CreateObject delegate. I would do it like this:

    public delegate object CreateObject(params object[] args);
    

    Then, here's a good way to generate a CreateObject delegate from a ConstructorInfo (using expression trees):

    /// <summary>
    ///    Creates and compiles an Expression like this:
    /// (object[] args) =>
    /// (object)(
    ///     new {ConstructedType}(args[0], args[1], ...)
    /// )
    /// </summary>
    public static DynamicStaticMethod CreateConstructor(ConstructorInfo constructor)
    {
        if (constructor == null) throw new ArgumentNullException("constructor");
    
        //Create 'args' parameter expression
        ParameterExpression argsParameter = Expression.Parameter(typeof(object[]), "args");
    
        //Create body expression
        ParameterInfo[] constructorParams = constructor.GetParameters();
        Expression body = Expression.New(
            constructor,
            CreateParameterExpressions(constructorParams, argsParameter)
        );
    
        //Create and compile lambda
        var lambda = Expression.Lambda<CreateObject>(
            Expression.Convert(body, typeof(object)),
            argsParameter
        );
        return lambda.Compile();
    }
    

    A full extended version of this compiler is in my personal repository. Feel free to browse whatever is there.

    Update: repository link changed.

    0 讨论(0)
  • 2020-12-07 16:43

    I've been doing a bit of testing with this and as a follow-up to Miron's original article (here), I've discovered that the .NET 4.0 Activator is much faster than before. Some results from a version of his app tweaked to show timings in milliseconds:

    .NET 3.5 build
    
    Number of iterates: 1000000
    Activator.CreateInstance(Type):                           4150
    Activator.CreateInstance<T>():                            1288
    FastObjectFactory.CreateObjec (empty cache):                33
    FastObjectFactory.CreateObjec (cache full):                 28
    ItemFactory.GetNewItem:                                   1283
    
    
    .NET 4.0 build
    
    Number of iterates: 1000000
    Activator.CreateInstance(Type):                            138
    Activator.CreateInstance<T>():                             151
    FastObjectFactory.CreateObjec (empty cache):                28
    FastObjectFactory.CreateObjec (cache full):                 22
    ItemFactory.GetNewItem:                                    156
    

    However this was for a no-parameter constructor and I also noticed that the activator is still a little slow when constructors with parameters are used, as can bse seen below.

    One problem I had with the original solution posted here is that I don't necessarily know the type of the objects I want at compile time - I only have a Type reference. Now (unless I'm being a duffer) that means I can't use the generic solution here or a simple variation on it.

    So this is a version I've knocked together which addresses the problem. It also showed up the slight slowness in the .NET 4.0 Activator when constructor parameters are used:

    // For use with no-parameter constructors. Also contains constants and utility methods
    public static class FastActivator
    {
        // THIS VERSION NOT THREAD SAFE YET
        static Dictionary<Type, Func<object>> constructorCache = new Dictionary<Type, Func<object>>();
    
        private const string DynamicMethodPrefix = "DM$_FastActivator_";
    
        public static object CreateInstance(Type objType)
        {
            return GetConstructor(objType)();
        }
    
        public static Func<object> GetConstructor(Type objType)
        {
            Func<object> constructor;
            if (!constructorCache.TryGetValue(objType, out constructor))
            {
                constructor = (Func<object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<object>), new Type[] { });
                constructorCache.Add(objType, constructor);
            }
            return constructor;
        }
    
        public static object BuildConstructorDelegate(Type objType, Type delegateType, Type[] argTypes)
        {
            var dynMethod = new DynamicMethod(DynamicMethodPrefix + objType.Name + "$" + argTypes.Length.ToString(), objType, argTypes, objType);
            ILGenerator ilGen = dynMethod.GetILGenerator();
            for (int argIdx = 0; argIdx < argTypes.Length; argIdx++)
            {
                ilGen.Emit(OpCodes.Ldarg, argIdx);
            }
            ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(argTypes));
            ilGen.Emit(OpCodes.Ret);
            return dynMethod.CreateDelegate(delegateType);
        }
    }
    
    // For use with one-parameter constructors, argument type = T1
    public static class FastActivator<T1>
    {
        // THIS VERSION NOT THREAD SAFE YET
        static Dictionary<Type, Func<T1, object>> constructorCache = new Dictionary<Type, Func<T1, object>>();
        public static object CreateInstance(Type objType, T1 arg1)
        {
            return GetConstructor(objType, new Type[] { typeof(T1) })(arg1);
        }
        public static Func<T1, object> GetConstructor(Type objType, Type[] argTypes)
        {
            Func<T1, object> constructor;
            if (!constructorCache.TryGetValue(objType, out constructor))
            {
                constructor = (Func<T1, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, object>), argTypes);
                constructorCache.Add(objType, constructor);
            }
            return constructor;
        }
    }
    
    // For use with two-parameter constructors, argument types = T1, T2
    public static class FastActivator<T1, T2>
    {
        // THIS VERSION NOT THREAD SAFE YET
        static Dictionary<Type, Func<T1, T2, object>> constructorCache = new Dictionary<Type, Func<T1, T2, object>>();
        public static object CreateInstance(Type objType, T1 arg1, T2 arg2)
        {
            return GetConstructor(objType, new Type[] { typeof(T1), typeof(T2) })(arg1, arg2);
        }
    
        public static Func<T1, T2, object> GetConstructor(Type objType, Type[] argTypes)
        {
            Func<T1, T2, object> constructor;
            if (!constructorCache.TryGetValue(objType, out constructor))
            {
                constructor = (Func<T1, T2, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, T2, object>), argTypes);
                constructorCache.Add(objType, constructor);
            }
            return constructor;
        }
    }
    
    // For use with three-parameter constructors, argument types = T1, T2, T3
    // NB: could possibly merge these FastActivator<T1,...> classes and avoid generic type parameters
    // but would need to take care that cache entries were keyed to distinguish constructors having 
    // the same number of parameters but of different types. Keep separate for now.
    public static class FastActivator<T1, T2, T3>
    {
        // THIS VERSION NOT THREAD SAFE YET
        static Dictionary<Type, Func<T1, T2, T3, object>> constructorCache = new Dictionary<Type, Func<T1, T2, T3, object>>();
        public static object CreateInstance(Type objType, T1 arg1, T2 arg2, T3 arg3)
        {
            return GetConstructor(objType, new Type[] { typeof(T1), typeof(T2), typeof(T3) })(arg1, arg2, arg3);
        }
    
        public static Func<T1, T2, T3, object> GetConstructor(Type objType, Type[] argTypes)
        {
            Func<T1, T2, T3, object> constructor;
            if (!constructorCache.TryGetValue(objType, out constructor))
            {
                constructor = (Func<T1, T2, T3, object>)FastActivator.BuildConstructorDelegate(objType, typeof(Func<T1, T2, T3, object>), argTypes);
                constructorCache.Add(objType, constructor);
            }
            return constructor;
        }
    }
    

    Some performance results below. Note this is for creation of 1 million objects and timings in milliseconds again:

    Activator.CreateInstance(objType) - parameterless constructor: 153
    FastActivator.CreateInstance(objType) - parameterless constructor: 86
    Using FastActivator.GetConstructor and calling it repeatedly - parameterless constructor: 34
    Activator.CreateInstance(objType) with 1 constructor arg: 3183
    FastActivator.CreateInstance(objType) with 1 constructor arg: 257
    FastActivator.GetConstructor and calling it repeatedly with 1 constructor arg: 126
    Activator.CreateInstance(objType) with 3 constructor args: 4403
    FastActivator.CreateInstance(objType) with 3 constructor args: 640
    FastActivator.GetConstructor and calling it repeatedly with 3 constructor args : 405
    FastActivator.GetConstructor and calling it repeatedly with 3 constructor args; args created only once : 19
    
    0 讨论(0)
  • 2020-12-07 16:44

    I'm putting this up as the so far best performant object creation factory so far using the current (2010-01-11) answers, according to my tests. I did notice that using cache works best when iterations are somewhere below 99,999. If you are going to load more than 99,999 it is best to not use cache. Because this could be the case I've created something that would allow you to use cache or not. My current project will sometimes load millions of records and at other times only load one. Anyways, I'm putting this out there to see what your thoughts are. Note that the code below is for ctor's that have 1 arg, one would have to create a similar factory for more than 1 arg ctor.

    
    // code updated 2010-06-01
    // class that creates comment objects
    public class CreatesSomeObject
    {
        // method that creates a comment object
        public void CreateComment()
        {
    
            // Method 1 (without cache)
            Comment comment1 = ObjectFactoryFactory<Comment, ObjectId>
                .CreateObject.Invoke(new ObjectId());
    
            // Method 2 (with cache)
            Comment comment2 = ObjectFactoryFactory<Comment, ObjectId>
                .CreateObjectWithCache.Invoke(new ObjectId());
    
            // Method 3 (without helper factory ObjectFactoryFactory)
            Comment comment3 = ObjectFactory<Comment, ObjectId>
                .CreateObject.Invoke(new ObjectId());
        }
    }
    
    // This is optional class. Just helps in creating objects when
    // a cache is needed or not needed.
    public static class ObjectFactoryFactory<T, P1> where T : class
    {
        static Hashtable cache = Hashtable.Synchronized(new Hashtable());
    
        public static Func<P1, T> CreateObject
        {
            get { return ObjectFactory<T, P1>.CreateObject; }
        }
    
        public static Func<P1, T> CreateObjectWithCache
        {
            get
            {
                return ObjectFactory<T, P1>.UseCache(cache);
            }
        }
    }
    
    // Main object creation factory class.
    public static class ObjectFactory<T, P1> where T : class
    {
    
        static Func<P1, T> _createObject;
    
        public static Func<P1, T> CreateObject
        {
            get
            {
                if (_createObject != null) return _createObject;
                _createObject = CreateDelegate();
                return _createObject;
            }
        }
    
        static Func<P1, T> CreateDelegate()
        {
            Type objType = typeof(T);
            Type[] types = new[] { typeof(P1) };
            var dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + 
                objType.Name, objType, types, objType);
            ILGenerator ilGen = dynMethod.GetILGenerator();
            // if need more than 1 arg add another Ldarg_x
            // you'll also need to add proper generics and 
            // CreateDelegate signatures
            ilGen.Emit(OpCodes.Ldarg_0);
            ilGen.Emit(OpCodes.Newobj, objType.GetConstructor(types));
            ilGen.Emit(OpCodes.Ret);
            return (Func<P1, T>)dynMethod.CreateDelegate(typeof(Func<P1, T>));
        }
    
        public static Func<P1, T> UseCache(Hashtable cache) 
        { 
            Type t = typeof(T);
            Func<P1, T> c = cache[t] as Func<P1, T>;
            if (c == null) 
            { 
                lock (cache.SyncRoot) 
                {
                    c = cache[t] as Func<P1, T>;
                    if (c != null) 
                    { 
                        return c; 
                    } 
                    c = CreateDelegate(); 
                    cache.Add(t, c); 
                } 
    
            } 
            return c; 
        }
    }
    
    0 讨论(0)
提交回复
热议问题