[C#] Expressions et Emit en alternatives à la Reflection

Reflection

On a l’habitude d’utiliser « Activator.CreateInstance », « property.GetValue », « property.SetValue »… on peut avoir envie d’améliorer les performances. Plusieurs possibilités.

Exemple: appel du constructeur par défaut (on peut en plus utiliser un cache)

public Func<T> CreateDefaultConstructor<T>(Type type)
{
    ConstructorInfo constructorInfo = type
        .GetConstructors(BindingFlags.Instance | BindingFlags.Public)
        .SingleOrDefault(c => !c.GetParameters().Any());

    return () => (T)constructorInfo.Invoke(null);
}

Autres possibilités utiliser les Expressions Linq ou System.Reflection.Emit

Expressions Linq

Création + cache avec les Expressions

private static Dictionary<Type, Func<object>> constructorCache = new Dictionary<Type, Func<object>>();

public Func<object> CreateDefaultConstructor(Type type)
{
    var expression = Expression.New(type);
    var lambda = Expression.Lambda<Func<object>>(expression);
    return lambda.Compile();
}

public override object CreateInstance(Type type)
{
    if (constructorCache.TryGetValue(type, out Func<object> cached))
    {
        return cached();
    }
    else
    {
        var ctor = CreateDefaultConstructor(type);
        constructorCache.Add(type, ctor);
        return ctor();
    }
}

Getter / Setter avec les Expressions

public Delegate CreateGetter(PropertyInfo property)
{
    var parameter = Expression.Parameter(property.DeclaringType, "o");
    Type delegateType = typeof(Func<,>).MakeGenericType(property.DeclaringType, property.PropertyType);
    var lambda = Expression.Lambda(delegateType, Expression.Property(parameter, property.Name), parameter);
    return lambda.Compile();
}

public Delegate CreateSetter(PropertyInfo property)
{
    var parameter = Expression.Parameter(property.DeclaringType, "o");
    var valueParm = Expression.Parameter(property.PropertyType, "value");
    Type delegateType = typeof(Action<,>).MakeGenericType(property.DeclaringType, property.PropertyType);
    var lambda = Expression.Lambda(delegateType, Expression.Assign(Expression.Property(parameter, property.Name), valueParm), parameter, valueParm);
    return lambda.Compile();
}

Toutefois j’ai observé des performances bien meilleures avec les « versions Generics », comme le fait Json.Net

public class ExpressionValueProvider : ValueProvider
{
    public Func<object, object> GetterDelegate { get; internal set; }
    public Action<object, object> SetterDelegate { get; internal set; }

    public ExpressionValueProvider(PropertyInfo property)
        : base(property)
    { }

    public Func<T, object> CreateGetter<T>()
    {
        Type instanceType = typeof(T);
        Type resultType = typeof(object);

        ParameterExpression parameterExpression = Expression.Parameter(instanceType, "instance");
        Expression resultExpression;

        MethodInfo getMethod = property.GetGetMethod(true);

        if (getMethod.IsStatic)
        {
            resultExpression = Expression.MakeMemberAccess(null, property);
        }
        else
        {
            Expression readParameter = EnsureCastExpression(parameterExpression, property.DeclaringType);

            resultExpression = Expression.MakeMemberAccess(readParameter, property);
        }

        resultExpression = EnsureCastExpression(resultExpression, resultType);

        LambdaExpression lambdaExpression = Expression.Lambda(typeof(Func<T, object>), resultExpression, parameterExpression);

        Func<T, object> compiled = (Func<T, object>)lambdaExpression.Compile();
        return compiled;
    }

    public Action<T, object> CreateSetter<T>()
    {
        Type instanceType = typeof(T);
        Type valueType = typeof(object);

        ParameterExpression instanceParameter = Expression.Parameter(instanceType, "instance");

        ParameterExpression valueParameter = Expression.Parameter(valueType, "value");
        Expression readValueParameter = EnsureCastExpression(valueParameter, property.PropertyType);

        MethodInfo setMethod = property.GetSetMethod(true);

        Expression setExpression;
        if (setMethod.IsStatic)
        {
            setExpression = Expression.Call(setMethod, readValueParameter);
        }
        else
        {
            Expression readInstanceParameter = EnsureCastExpression(instanceParameter, property.DeclaringType);

            setExpression = Expression.Call(readInstanceParameter, setMethod, readValueParameter);
        }

        LambdaExpression lambdaExpression = Expression.Lambda(typeof(Action<T, object>), setExpression, instanceParameter, valueParameter);

        Action<T, object> compiled = (Action<T, object>)lambdaExpression.Compile();
        return compiled;
    }

    private Expression EnsureCastExpression(Expression expression, Type targetType, bool allowWidening = false)
    {
        Type expressionType = expression.Type;

        if (expressionType == targetType || (!expressionType.IsValueType && targetType.IsAssignableFrom(expressionType)))
        {
            return expression;
        }

        if (targetType.IsValueType)
        {
            Expression convert = Expression.Unbox(expression, targetType);

            if (allowWidening && targetType.IsPrimitive)
            {
                MethodInfo toTargetTypeMethod = typeof(Convert)
                    .GetMethod("To" + targetType.Name, new[] { typeof(object) });

                if (toTargetTypeMethod != null)
                {
                    convert = Expression.Condition(
                        Expression.TypeIs(expression, targetType),
                        convert,
                        Expression.Call(toTargetTypeMethod, expression));
                }
            }

            return Expression.Condition(
                Expression.Equal(expression, Expression.Constant(null, typeof(object))),
                Expression.Default(targetType),
                convert);
        }

        return Expression.Convert(expression, targetType);
    }

    public override object GetValue(object obj)
    {
        if (GetterDelegate == null)
        {
            GetterDelegate = CreateGetter<object>();
        }

        return GetterDelegate.Invoke(obj);
    }

    public override void SetValue(object obj, object value)
    {
        if (SetterDelegate == null)
        {
            SetterDelegate = CreateSetter<object>();
        }

        SetterDelegate.Invoke(obj, value);
    }
}

Emit

Exemple: constructor

public Func<T> CreateDefaultConstructor<T>(Type type)
{
    DynamicMethod dynMethod = new DynamicMethod("CreateInstance", type, null);
    ILGenerator ilGen = dynMethod.GetILGenerator();
    ilGen.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));
    ilGen.Emit(OpCodes.Ret);
    return (Func<T>)dynMethod.CreateDelegate(typeof(Func<T>));
}

Getter / Setter

public class DynamicValueProvider : ValueProvider
{
    public Func<object, object> GetterDelegate { get; internal set; }
    public Action<object, object> SetterDelegate { get; internal set; }

    public DynamicValueProvider(PropertyInfo property)
        : base(property)
    { }

    public Action<object, object> CreateSetter()
    {
        var type = property.DeclaringType;
        DynamicMethod setter = new DynamicMethod("Set", typeof(void), new Type[] { typeof(object), typeof(object) });
        ILGenerator ilSet = setter.GetILGenerator();
        // Load first argument to the stack and cast it
        ilSet.Emit(OpCodes.Ldarg_0);
        ilSet.Emit(OpCodes.Castclass, type);
        // Load second argument to the stack and cast it or unbox it
        ilSet.Emit(OpCodes.Ldarg_1);
        if (property.PropertyType.IsValueType)
        {
            ilSet.Emit(OpCodes.Unbox_Any, property.PropertyType);
        }
        else
        {
            ilSet.Emit(OpCodes.Castclass, property.PropertyType);
        }
        // Call Setter method and return
        ilSet.Emit(OpCodes.Callvirt, property.GetSetMethod());
        ilSet.Emit(OpCodes.Ret);

        return (Action<object, object>)setter.CreateDelegate(typeof(Action<object, object>));
    }

    public Func<object, object> CreateGetter()
    {
        var type = property.DeclaringType;
        DynamicMethod getter = new DynamicMethod("Get", typeof(object), new Type[] { typeof(object), });
        ILGenerator ilGet = getter.GetILGenerator();
        // Load first argument to the stack
        ilGet.Emit(OpCodes.Ldarg_0);
        // Cast the object to the type
        ilGet.Emit(OpCodes.Castclass, type);
        // Call the getter method passing the object on teh stack as the this reference
        ilGet.Emit(OpCodes.Callvirt, property.GetGetMethod());
        // If the property type is a value type (int/DateTime/..)
        // box the value so we can return it
        if (property.PropertyType.IsValueType)
        {
            ilGet.Emit(OpCodes.Box, property.PropertyType);
        }
        // Return from the method
        ilGet.Emit(OpCodes.Ret);

        return (Func<object, object>)getter.CreateDelegate(typeof(Func<object, object>));
    }

    public override object GetValue(object obj)
    {
        if (GetterDelegate == null)
        {
            GetterDelegate = CreateGetter();
        }

        return GetterDelegate.Invoke(obj);
    }

    public override void SetValue(object obj, object value)
    {
        if (SetterDelegate == null)
        {
            SetterDelegate = CreateSetter();
        }

        SetterDelegate.Invoke(obj, value);
    }
}

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *