反射性能 - 创建委托(C#属性)

29

我在使用反射时遇到了性能问题。
因此,我决定为我的对象的属性创建委托,并且目前已经做到了这一点:

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();
}

结果非常满意,比传统方法(PropertyInfo.GetValue(obj, null);)快30-40倍。

问题是:我怎样才能做到使SetValue以同样的方式工作?不幸的是,我没有找到方法。

我这么做是因为我的应用程序结构不允许我使用带有<T>的方法。


我这样做是因为我的应用程序结构无法使用带有“<T>”的方法。这是否意味着你的NETFX版本小于2.0?为什么你的应用程序不能使用泛型? - Josh E
此外,为属性创建委托与反射有什么关系?你试图使用反射解决什么问题? - Josh E
委托具有更好的性能,并且可以动态使用。当您需要使用动态调用时,它们是首选选项。 - GregRos
@JoshE 明显不是小于2.0版本,因为他使用了泛型和lambda表达式。但我和你的想法一样。你最终想要做什么呢? - aquinas
是的,我可以使用.NET 4.0,但不适用于我的应用程序所有逻辑。而且需要进行许多更改。特别是WebServices之间的通信,我的应用程序中没有应用“<T>”方法的点,如load、save、update、insert等。 - J. Lennon
显示剩余3条评论
3个回答

21

如果性能是关键,我建议你使用CreateDelegate结构。因为你预先知道方法的签名,也就是PropertyInfo中的GetGetMethodGetSetMethod,所以可以创建一个委托直接执行相同签名的方法。如果你需要构建一些逻辑(没有方法句柄)到委托,表达式会更适合。我对不同的解决方法进行了一些基准测试:

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

如果我是你,我会写:

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;
}

现在你需要调用:

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

这不是更简单吗?我在这里写了一个通用类处理这个确切的事情。

21
这对你来说应该行得通:
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();
}

使用方法:

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

使用TestClass

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

输出:

foo


1

使用动态类型。它们在幕后使用反射,但速度要快得多。

否则...

有很多免费的更快的反射库,具有宽松的许可证。我可以给你链接,但是太多了,我不确定哪一个适合你。只需搜索codeplex等。当您找到喜欢的东西时,请尝试一下。

但是,是的,在此之前,考虑一下反射是否真的是答案。通常还有其他解决方案。

编辑:按要求...

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

据我所知,这是常识。


1
在我看来,除非没有其他方法,否则不应该使用动态反射。你能发布一些支持“它们更快”的数据吗?...以及一个按照OP请求使用动态反射的示例?这些可能是负投票的原因之一... - IAbstract
2
我理解中的dynamics是指例如 dynamic foo = bar;。Dynamics这个主题非常广泛——如果你正在参考创建ExpressionTree对象,则是的——这就是dynamics所用的方式 :) - IAbstract
1
据我所知,C#中只有一种被称为“动态类型”的东西,那就是我所说的。DLR专门用于动态调用方法和属性。直到现在我才知道,DLR在内部除了使用反射外,还使用表达式树。如果您查看上面的链接,您会发现使用动态类型调用方法比使用MethodBase.Invoke快几个数量级。它也比构造表达式树更简单、更简洁。 - GregRos
http://msdn.microsoft.com/en-us/magazine/cc163759.aspx 委托调用是最受推荐的... - J. Lennon
1
本文的部分内容基于.NET Framework 2.0的预发布版本。这些部分可能会发生变化。那是一篇早在dynamic出现之前就过时的文章。然而,我并不否认DLR不是调用方法最快的方式。我想说的是,它比反射快得多,并且使用起来非常简单。 - GregRos
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接