Search code examples
c#reflectionlinq-expressions

Get accessors from PropertyInfo as Func<object> and Action<object> delegates


I need to call properties that are determined at runtime through reflection and they are called at a high frequency. So I am looking for solution with optimal performance, which mean I'd probably avoid reflection. I was thinking of storing the property accessors as Func and Action delegates in a list and then call those.

private readonly Dictionary<string, Tuple<Func<object>, Action<object>>> dataProperties =
        new Dictionary<string, Tuple<Func<object>, Action<object>>>();

private void BuildDataProperties()
{
    foreach (var keyValuePair in this.GetType()
        .GetProperties(BindingFlags.Instance | BindingFlags.Public)
        .Where(p => p.Name.StartsWith("Data"))
        .Select(
            p =>
                new KeyValuePair<string, Tuple<Func<object>, Action<object>>>(
                    p.Name,
                    Tuple.Create(this.GetGetter(p), this.GetSetter(p)))))
    {
        this.dataProperties.Add(keyValuePair.Key, keyValuePair.Value);
    }
}

The question now is, how do I get the accessor delagates as Func and Action delgates for later invokation?

A naïve implementation that still uses reflection for the invocation would look like this:

private Func<object> GetGetter(PropertyInfo info)
{
    // 'this' is the owner of the property
    return () => info.GetValue(this);
}

private Action<object> GetSetter(PropertyInfo info)
{
    // 'this' is the owner of the property
    return v => info.SetValue(this, v);
}

How could I implement the above methods without refelections. Would expressions be the fastest way? I have tried using expression like this:

private Func<object> GetGetter(PropertyInfo info)
{
    // 'this' is the owner of the property
    return
        Expression.Lambda<Func<object>>(
            Expression.Convert(Expression.Call(Expression.Constant(this), info.GetGetMethod()), typeof(object)))
            .Compile();
}

private Action<object> GetSetter(PropertyInfo info)
{
    // 'this' is the owner of the property
    var method = info.GetSetMethod();
    var parameterType = method.GetParameters().First().ParameterType;
    var parameter = Expression.Parameter(parameterType, "value");
    var methodCall = Expression.Call(Expression.Constant(this), method, parameter);

    // ArgumentException: ParameterExpression of type 'System.Boolean' cannot be used for delegate parameter of type 'System.Object'
    return Expression.Lambda<Action<object>>(methodCall, parameter).Compile();
}

But here the last line of GetSetter I get the following excpetion if the type of the property is not exactly of type System.Object:

ArgumentException: ParameterExpression of type 'System.Boolean' cannot be used for delegate parameter of type 'System.Object'


Solution

  • I think what you need to do is return the Lamda as the correct type, with object as the parameter, however do a conversion within the expression to the correct type before calling the setter:

     private Action<object> GetSetter(PropertyInfo info)
     {
         // 'this' is the owner of the property
         var method = info.GetSetMethod();
         var parameterType = method.GetParameters().First().ParameterType;
    
         // have the parameter itself be of type "object"
         var parameter = Expression.Parameter(typeof(object), "value");
    
         // but convert to the correct type before calling the setter
         var methodCall = Expression.Call(Expression.Constant(this), method, 
                            Expression.Convert(parameter,parameterType));
    
         return Expression.Lambda<Action<object>>(methodCall, parameter).Compile();
    
      }
    

    Live example: http://rextester.com/HWVX33724