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