Скомпилируйте LambdaExpression с дополнительным параметром - PullRequest
1 голос
/ 30 октября 2019

Я пытаюсь получить объект MethodInfo и вызвать Invoke () для него. Вот что я пробовал до сих пор:

public class ContactCustomFieldsFacet
{
    public Dictionary<string, string> Fields { get; set; } = new Dictionary<string, string>();

}

public static class FacetExtensions
{
    public static Func<ContactCustomFieldsFacet, object> Compile(string body)
    {
        ParameterExpression prm = Expression.Parameter(typeof(ContactCustomFieldsFacet), typeof(ContactCustomFieldsFacet).Name);
        LambdaExpression exp = DynamicExpressionParser.ParseLambda(new[] { prm }, typeof(object), body);
        return (Func<ContactCustomFieldsFacet, object>)exp.Compile();
    }
}

var lambda = FacetExtensions.Compile($"ContactCustomFieldsFacet.Fields[\"test\"]");
var propertyMethod = lambda.Method;
// No control over the below code - it's called by a third party library
ContactCustomFieldsFacet facet = GetFacet();
propertyMethod.Invoke(null, new [] { facet }); // System.ArgumentException: MethodInfo must be a runtime MethodInfo object.

По сути, я хочу иметь возможность динамически создавать выражение, в котором для этого экземпляра жестко задано поле индекса словаря ContactCustomFieldsFacet.Fields. Я понимаю, что могу ошибаться, поэтому любые указатели будут оценены.

.NET Fiddle: https://dotnetfiddle.net/ecMRye

РЕДАКТИРОВАТЬ

БлагодаряКори Я обновил код, но все еще получаю похожую проблему

.NET Fiddle: https://dotnetfiddle.net/5hDOsd

1 Ответ

2 голосов
/ 30 октября 2019

Поиск метода, который вы хотите вызвать, является достаточно простым отражением. Сложной частью является выяснение имени метода для вызова. Иногда самое простое, что можно сделать, - это создать выражение и посмотреть, как его компилятор соединяет.

Например, это:

Expression<Func<Dictionary<string, string>, string>> expr = d => d["test"];

Если вы проверяете (или Dump(), если вы используете LINQPad) переменную expr, вы сможете бродить по сгенерированному дереву выражений и смотреть на такие вещи, как информация о методе в узле Call.

В вашем случаекажется, что вы хотите вызвать get_Item в классе Dictionary<string, string>. Требуется ссылка на объект и строковый параметр для ключа. Экземпляр объекта будет значением свойства Fields из предоставленного ContactCustomFieldsFacet, поэтому нам нужно его получить.

Сборка всего объекта вручную может выглядеть примерно так:

public static Func<ContactCustomFieldsFacet, string> MakeAccessor(string key)
{
    var tFacet = typeof(ContactCustomFieldsFacet);
    var piFields = tFacet.GetProperty("Fields");
    var miFieldsGet = piFields.PropertyType.GetMethod("get_Item");

    var param = Expression.Parameter(tFacet, "facet");
    var lambda = Expression.Lambda<Func<ContactCustomFieldsFacet, object>>
    (
        Expression.Call
        (
            // Instance for invocation: param.Fields
            Expression.MakeMemberAccess(param, piFields),
            // Method to call
            miFieldsGet,
            // Call arguments
            Expresion.Constant(key)
        ),
        param
    );

    return lambda.Compile();
}

При вызове этого метода вы получите Func<>, который возвращает значение для определенного key, установленного во время создания Func<>. Например:

var fnGetTest = MakeAccessor("test");
var value = fnGetTest(someFacetInstance);

Все это предполагает, что в словаре хранятся ссылочные типы - строка работает нормально. Если это тип значения (например, int), вам нужно указать значение Convert, равное object.

...