Reflection Performance - Create Delegate (Properti

2019-01-13 08:21发布

问题:

I'm having performance problems with using reflection.
So I decided to create delegates for the properties of my objects and so far got this:

TestClass cwp = new TestClass();
var propertyInt = typeof(TestClass).GetProperties().Single(obj => obj.Name == "AnyValue");
var access = BuildGetAccessor(propertyInt.GetGetMethod());
var result = access(cwp);
static Func<object, object> BuildGetAccessor(MethodInfo method)
{
    var obj = Expression.Parameter(typeof(object), "o");

    Expression<Func<object, object>> expr =
        Expression.Lambda<Func<object, object>>(
            Expression.Convert(
                Expression.Call(
                    Expression.Convert(obj, method.DeclaringType),
                    method),
                typeof(object)),
            obj);

    return expr.Compile();
}

The results were highly satisfactory, about 30-40 times faster than using the conventional method (PropertyInfo.GetValue (obj, null);)

The problem is: How can I make a SetValue of a property, which works the same way? Unfortunately did not get a way.

I am doing so because I can not use methods with <T> because of the structure of my application.

回答1:

This should work for you:

static Action<object, object> BuildSetAccessor(MethodInfo method)
{
    var obj = Expression.Parameter(typeof(object), "o");
    var value = Expression.Parameter(typeof(object));

    Expression<Action<object, object>> expr =
        Expression.Lambda<Action<object, object>>(
            Expression.Call(
                Expression.Convert(obj, method.DeclaringType),
                method,
                Expression.Convert(value, method.GetParameters()[0].ParameterType)),
            obj,
            value);

    return expr.Compile();
}

Usage:

var accessor = BuildSetAccessor(typeof(TestClass).GetProperty("MyProperty").GetSetMethod());
var instance = new TestClass();
accessor(instance, "foo");
Console.WriteLine(instance.MyProperty);

With TestClass:

public class TestClass 
{
    public string MyProperty { get; set; }
}

Prints out:

foo



回答2:

I think you'd be better off with CreateDelegate construct if performance is the key. Since you know the signature of the method beforehand, which here is just GetGetMethod and GetSetMethod of the PropertyInfo, you can create a delegate to execute the very method with the same signature directly. Expressions would be better suited if you need to build some logic (for which you did not have a method handle) to delegates. I did some benchmarking on different routes to this problem:

Func<S, T> Getter;
Action<S, T> Setter;
PropertyInfo Property;
public void Initialize(Expression<Func<S, T>> propertySelector)
{
    var body = propertySelector.Body as MemberExpression;
    if (body == null)
        throw new MissingMemberException("something went wrong");

    Property = body.Member as PropertyInfo;



    //approaches:

    //Getter = s => (T)Property.GetValue(s, null);

    //Getter = memberSelector.Compile();

    //ParameterExpression inst = Expression.Parameter(typeof(S));
    //Getter = Expression.Lambda<Func<S, T>>(Expression.Property(inst, Property), inst).Compile();

    //var inst = Expression.Parameter(typeof(S));
    //Getter = Expression.Lambda<Func<S, T>>(Expression.Call(inst, Property.GetGetMethod()), inst).Compile();

    //Getter = (Func<S, T>)Delegate.CreateDelegate(typeof(Func<S, T>), Property.GetGetMethod());



    //Setter = (s, t) => Property.SetValue(s, t, null);

    //var val = Expression.Parameter(typeof(T));
    //var inst = Expression.Parameter(typeof(S));
    //Setter = Expression.Lambda<Action<S, T>>(Expression.Call(inst, Property.GetSetMethod(), val),
    //                                         inst, val).Compile();

    //Setter = (Action<S, T>)Delegate.CreateDelegate(typeof(Action<S, T>), Property.GetSetMethod());
}


//Actual calls (tested under loop):
public T Get(S instance)
{
    //direct invocation:
    //return (T)Property.GetValue(instance, null);

   //calling the delegate:
   //return Getter(instance);
}
public void Set(S instance, T value)
{
    //direct invocation:
    //Property.SetValue(instance, value, null);

   //calling the delegate:
   //Setter(instance, value);
}

Results for about 10000000 calls - (Get, Set):

GetValue-SetValue (direct): 3800 ms, 5500 ms

GetValue-SetValue (delegate): 3600 ms, 5300 ms

compiled expressions:

   Get: Expression.Property: 280 ms

        Expression.Call: 280 ms

        direct compile: 280 ms
   Set: 300 ms

create delegate: 130 ms, 135 ms

direct property call: 70 ms, 70 ms

I would, in your case, write:

public static Func<S, T> BuildGetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
    return propertySelector.GetPropertyInfo().GetGetMethod().CreateDelegate<Func<S, T>>();
}

public static Action<S, T> BuildSetAccessor<S, T>(Expression<Func<S, T>> propertySelector)
{
    return propertySelector.GetPropertyInfo().GetSetMethod().CreateDelegate<Action<S, T>>();
}

// a generic extension for CreateDelegate
public static T CreateDelegate<T>(this MethodInfo method) where T : class
{
    return Delegate.CreateDelegate(typeof(T), method) as T;
}

public static PropertyInfo GetPropertyInfo<S, T>(this Expression<Func<S, T>> propertySelector)
{
    var body = propertySelector.Body as MemberExpression;
    if (body == null)
        throw new MissingMemberException("something went wrong");

    return body.Member as PropertyInfo;
}

So now you call:

TestClass cwp = new TestClass();
var access = BuildGetAccessor((TestClass t) => t.AnyValue);
var result = access(cwp);

Isn't that simpler?? Had written a generic class here to handle the exact thing.



回答3:

Use dynamic types. They use reflection under the hood, but they're a lot faster.

Otherwise...

There are tons of free faster reflection libraries out there with permissive licenses. I would link you, but there are too many, and I'm not sure which would suit you. Just search codeplex etc. When you find something you like, try it out.

But yeah, maybe before that, think if reflection really is the answer. Often there are other solutions.

Edit: As Requested...

http://geekswithblogs.net/SunnyCoder/archive/2009/06/26/c-4.0-dynamics-vs.-reflection.aspx
http://theburningmonk.com/2010/09/performance-test-dynamic-method-invocation-in-csharp-4/
http://www.mssoftwareconsulting.com/msswc/blog/post/C-40-and-dynamic-performance.aspx

It's common knowledge as far as I can tell.