vendredi 23 mars 2018

Create an object with reflection using lambda expressions

We are using a lot of reflection by calling Activator.CreateInstance to create new objects in our code, but based on this article it's better using the compiled lambda expression to improve the performance. So I created a static function that creates an intance of a class with lambda expressions :

    public static class ClassBuilder
    {

        private delegate T ObjectActivator<T>(params object[] args);

        /// <summary>
        /// This function will create a concrete object of type T
        /// </summary>
        /// <typeparam name="T">Base or concrete of object to return</typeparam>
        /// <param name="type">Concrete type of the object to create</param>
        /// <param name="parameters">paramters to give to the constructor</param>
        /// <returns>Instance of the concrete object</returns>
        public static T CreateInstance<T>(Type type, params object[] parameters)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }
            if (parameters == null)
            {
                throw new ArgumentNullException(nameof(parameters));
            }

            // get the concrete types of given params
            Type[] typedArgs = new Type[parameters.Length];

            for (int i = 0; i < parameters.Length; i++)
            {
                typedArgs[i] = parameters[i].GetType();
            }

            // get the right constructor depending the arguments
            ConstructorInfo ctor = type.GetConstructor(typedArgs);

            if (ctor != null)
            {
                // create the activator
                ObjectActivator<T> createdActivator = GetActivator<T>(ctor);

                // return the concrete object
                return createdActivator(parameters);
            }
            else
            {
                throw new ArgumentException("Unable to find constructor with specified parameters.");
            }
        }


        private static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
        {

            Type type = ctor.DeclaringType;
            ParameterInfo[] paramsInfo = ctor.GetParameters();

            // create parameter of name args as expression (type of args : object[])
            ParameterExpression param = Expression.Parameter(typeof(object[]), "args");

            // create expressions for all the parameters of the constructor with the right type
            Expression[] argsExp = new Expression[paramsInfo.Length];
            for (int i = 0; i < paramsInfo.Length; i++)
            {
                // get the type of the current parameter (parameter a position i)
                Expression idx = Expression.Constant(i);
                Type paramType = paramsInfo[i].ParameterType;

                Expression paramAccessorExp = Expression.ArrayIndex(param, idx);

                // Creates a UnaryExpression that represents a type conversion operation.
                argsExp[i] = Expression.Convert(paramAccessorExp, paramType);
            }

            // Creates a NewExpression that represents calling the specified constructor with the specified arguments.
            NewExpression newExp = Expression.New(ctor, argsExp);

            // Creates a LambdaExpression by first constructing a delegate type.
            LambdaExpression lambdaExpression = Expression.Lambda(typeof(ObjectActivator<T>), newExp, param);

            // Compile function will create a delegate function that represents the lamba expression
            ObjectActivator<T> compiledExpression = (ObjectActivator<T>)lambdaExpression.Compile();

            return compiledExpression;
        }       
    }

But after this implementation I tried to profile the 3 methods (Activator.CreateInstance, Inovke and Lambda Expression) by creating 1000 instances of an object. I was really disapointed of the result of the lambda expression.

Then I see in that blog that "... it is important to remember that compilation should be performed only once, so the code should be carefully reviewed to avoid occasional recompilcation of lambdas".

So I added a cache that takes the ctor info as the key of the dictionary as following :

    // declaration
    private static ConcurrentDictionary<object, object> _activatorCache = new ConcurrentDictionary<object, object>();


    private static ObjectActivator<T> GetActivator<T>(ConstructorInfo ctor)
    {
        // check if the object is in the cache before creating it
        if (_activatorCache.ContainsKey(ctor))
        {
            return _activatorCache[ctor] as ObjectActivator<T>;
        }


        Type type = ctor.DeclaringType;
        ParameterInfo[] paramsInfo = ctor.GetParameters();

        ...

        // Compile function will create a delegate function that represents the lamba expression
        ObjectActivator<T> compiledExpression = (ObjectActivator<T>)lambdaExpression.Compile();

        // add the compiled expression to the cache
        _activatorCache[ctor] = compiledExpression;
    }

It improves a lot the performance but still not concluent.

Is there something I'm doing wrong ?

Is that a good way to cache the compiled expression with the ctor info as a key ?





Aucun commentaire:

Enregistrer un commentaire