Expression.Lambda не может автоматически конвертировать типы - PullRequest
0 голосов
/ 06 июля 2018

У меня есть некоторые свойства во многих классах, и я хочу скомпилировать их методы получения / установки в лямбда-выражения, чтобы я мог использовать отражения с большей производительностью.

(Профилируется, и использование getValue / setValue в Reflection занимает около 78% от общего времени выполнения ...)

Однако, похоже, что Expression.Lambda () поддерживает только один набор параметров и не будет автоматически преобразовывать тип параметра.

using Exp = System.Linq.Expressions.Expression;
...
public class A { public int propA { get; set; } }
public class B { public int propB { get; set; } }
static Func<object, int> BuildFunc(Type type, string propName)
{
    var param = Exp.Parameter(prop.DeclaringType, "x");
    var exBody = Exp.Call(param, prop.GetGetMethod());
    return Exp.Lambda<Func<object, int>>(exBody, param).Compile();
}
...
var a = new A();
var b = new B();
var fA = BuildFunc(typeof(A).GetProperty("propA"));
var fB = BuildFunc(typeof(B).GetProperty("propB"));
fA(a);
fB(b);

Это вызовет исключение:

ParameterExpression типа __Main__+A нельзя использовать для параметра делегата типа System.Object

Если я изменю выражение на Exp.Lambda<Func<A, int>>(...), оно будет работать с классом A, но не будет работать с классом B.

Если я использую Expression.Convert для преобразования типов, он выдаст ArgumentException, который говорит мне, что метод не может быть вызван в случае экземпляра System.Object.

Итак, что я могу сделать, чтобы скомпилировать это выражение, как показано ниже, которое поддерживает любой тип объекта и соответствующий метод? lambda = (object obj, MethodInfo method, ...) => { method.Invoke(obj, ...) }

1 Ответ

0 голосов
/ 06 июля 2018

Существуют лучшие API-интерфейсы выражений для вызова методов получения / установки свойств. Вам определенно не нужно прибегать к MethodInfo.Invoke.

Преобразование из / в object обрабатывается Expression.Convert. Вам просто нужно подключить его в нужном месте.

Вот пример компиляции делегата getter / setter, где T - это тип вашего контейнера (A или B в вашем примере).

static Func<T, object> CompileGetter<T>(PropertyInfo property)
{
    // Target expression: (T obj) => (object)obj.Property;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    Expression body = Expression.Convert(Expression.MakeMemberAccess(objParam, property), typeof(object));

    return Expression
        .Lambda<Func<T, object>>(body, objParam)
        .Compile();
}

static Action<T, object> CompileSetter<T>(PropertyInfo property)
{
    // Target expression: (T obj, object value) => obj.Property = (TProperty)value;
    ParameterExpression objParam = Expression.Parameter(typeof(T), "obj");
    ParameterExpression valueParam = Expression.Parameter(typeof(object), "value");

    Expression body = Expression.Assign(
        Expression.MakeMemberAccess(objParam, property),
        Expression.Convert(valueParam, property.PropertyType)
    );

    return Expression
        .Lambda<Action<T, object>>(body, objParam, valueParam)
        .Compile();
}

Доказательство:

class Dummy
{
    public int Value { get; set; }
}

...

PropertyInfo prop = typeof(Dummy).GetProperty("Value");
Func<Dummy, object> getter = CompileGetter<Dummy>(prop);
Action<Dummy, object> setter = CompileSetter<Dummy>(prop);

Dummy d = new Dummy { Value = 123 };

Assert.AreEqual(123, getter(d));

setter(d, 321);

Assert.AreEqual(321, d.Value);

Обратите внимание: LambdaExpression.Compile является операцией, чрезвычайно интенсивно использующей процессор, поэтому, как только вы создадите свой делегат getter / setter, вы должны кэшировать его, чтобы получить желаемое повышение производительности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...