简体   繁体   English

从PropertyInfo获取访问器作为Func <object>和Action <object>委托

[英]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. 我在考虑将属性访问器存储为列表中的Func和Action委托,然后调用它们。

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? 现在的问题是,如何将访问器分离为Func和Action为以后的调用进行分类?

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. 如何在没有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 : 但是在这里GetSetter的最后一行如果属性的类型不完全是System.Object类型,我得到以下的GetSetter

ArgumentException: ParameterExpression of type 'System.Boolean' cannot be used for delegate parameter of type 'System.Object' ArgumentException:类型为'System.Boolean'的ParameterExpression不能用于'System.Object'类型的委托参数

This is my way, it's working fine. 这是我的方式,它工作正常。

But i dont know it's performance. 但我不知道它的表现。

    public static Func<object, object> GenerateGetterFunc(this PropertyInfo pi)
    {
        //p=> ((pi.DeclaringType)p).<pi>

        var expParamPo = Expression.Parameter(typeof(object), "p");
        var expParamPc = Expression.Convert(expParamPo,pi.DeclaringType);

        var expMma = Expression.MakeMemberAccess(
                expParamPc
                , pi
            );

        var expMmac = Expression.Convert(expMma, typeof(object));

        var exp = Expression.Lambda<Func<object, object>>(expMmac, expParamPo);

        return exp.Compile();
    }

    public static Action<object, object> GenerateSetterAction(this PropertyInfo pi)
    {
        //p=> ((pi.DeclaringType)p).<pi>=(pi.PropertyType)v

        var expParamPo = Expression.Parameter(typeof(object), "p");
        var expParamPc = Expression.Convert(expParamPo,pi.DeclaringType);

        var expParamV = Expression.Parameter(typeof(object), "v");
        var expParamVc = Expression.Convert(expParamV, pi.PropertyType);

        var expMma = Expression.Call(
                expParamPc
                , pi.GetSetMethod()
                , expParamVc
            );

        var exp = Expression.Lambda<Action<object, object>>(expMma, expParamPo, expParamV);

        return exp.Compile();
    }

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: 我认为你需要做的是将Lamda作为正确的类型返回,以object作为参数,但是在调用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 实例: http//rextester.com/HWVX33724

You need to use a convert method like Convert.ChangeType . 您需要使用Convert.ChangeType类的转换方法。 The type of property is bool. 物业的类型是布尔。 But the return type of GetSetter methos is object. 但GetSetter方法的返回类型是对象。 So you should convert property type that is bool in expression to object. 所以你应该将表达式中bool的属性类型转换为object。

    public static Action<T, object> GetSetter<T>(T obj, string propertyName)
    {
        ParameterExpression targetExpr = Expression.Parameter(obj.GetType(), "Target");
        MemberExpression propExpr = Expression.Property(targetExpr, propertyName);
        ParameterExpression valueExpr = Expression.Parameter(typeof(object), "value");
        MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(propExpr.Type));
        UnaryExpression valueCast = Expression.Convert(convertExpr, propExpr.Type);
        BinaryExpression assignExpr = Expression.Assign(propExpr, valueCast);
        return Expression.Lambda<Action<T, object>>(assignExpr, targetExpr, valueExpr).Compile();
    }

    private static Func<T, object> GetGetter<T>(T obj, string propertyName)
    {
        ParameterExpression arg = Expression.Parameter(obj.GetType(), "x");
        MemberExpression expression = Expression.Property(arg, propertyName);
        UnaryExpression conversion = Expression.Convert(expression, typeof(object));
        return Expression.Lambda<Func<T, object>>(conversion, arg).Compile();
    }

LIVE DEMO 现场演示

EDIT: 编辑:

public class Foo
{
    #region Fields

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

    #endregion

    #region Properties

    public string Name { get; set; }
    public string Data1 { get; set; }
    public string Data2 { get; set; }
    public string Data3 { get; set; }
    public int ID { get; set; }

    #endregion

    #region Methods: public

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

    #endregion

    #region Methods: private

    private Func<T, object> GetGetter<T>(T obj, string propertyName)
    {
        ParameterExpression arg = Expression.Parameter(obj.GetType(), "x");
        MemberExpression expression = Expression.Property(arg, propertyName);
        UnaryExpression conversion = Expression.Convert(expression, typeof(object));
        return Expression.Lambda<Func<T, object>>(conversion, arg).Compile();
    }

    private Action<T, object> GetSetter<T>(T obj, string propertyName)
    {
        ParameterExpression targetExpr = Expression.Parameter(obj.GetType(), "Target");
        MemberExpression propExpr = Expression.Property(targetExpr, propertyName);
        ParameterExpression valueExpr = Expression.Parameter(typeof(object), "value");
        MethodCallExpression convertExpr = Expression.Call(typeof(Convert), "ChangeType", null, valueExpr, Expression.Constant(propExpr.Type));
        UnaryExpression valueCast = Expression.Convert(convertExpr, propExpr.Type);
        BinaryExpression assignExpr = Expression.Assign(propExpr, valueCast);
        return Expression.Lambda<Action<T, object>>(assignExpr, targetExpr, valueExpr).Compile();
    }

    #endregion
}

You can get a value from dictionary like below : 您可以从字典中获取值,如下所示:

        var t = new Foo { ID = 1, Name = "Bla", Data1 = "dadsa"};
        t.BuildDataProperties();
        var value = t.dataProperties.First().Value.Item1(t);

LIVE DEMO 现场演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM