Создать лямбда-действие из выражения функции - PullRequest
7 голосов
/ 15 мая 2010

Относительно легко создать лямбда-функцию, которая будет возвращать значение свойства из объекта, даже включая глубокие свойства ...

Func<Category, string> getCategoryName = new Func<Category, string>(c => c.Name);

и это можно назвать следующим образом ...

string categoryName = getCategoryName(this.category);

Но, учитывая только результирующую функцию выше (или выражение, изначально использовавшееся для создания функции), любой может предоставить простой способ создания противоположного действия ...

Action<Category, string> setCategoryName = new Action<Category, string>((c, s) => c.Name = s);

... что позволит установить такое же значение свойства следующим образом?

setCategoryName(this.category, "");

Обратите внимание, что я ищу способ программно создать действие из функции или выражения - надеюсь, я показал, что уже знаю, как создать его вручную.

Я открыт для ответов, которые работают как в .net 3.5 и 4.0.

Спасибо.

UPDATE:

Возможно, я не совсем ясен в своем вопросе, поэтому позвольте мне попытаться продемонстрировать более четко, что я пытаюсь сделать.

У меня есть следующий метод (который я создал для целей этого вопроса) ...

void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) {

    Func<TObject, TValue> getValue = expression.Compile();
    TValue stuff = getValue(obj);

    Expression<Action<TObject, TValue>> assignmentExpression = (o, v) => Expression<TObject>.Assign(expression, Expression.Constant(v, typeof(TValue)));
    Action<TObject, TValue> setValue = assignmentExpression.Compile();

    setValue(obj, stuff);

}

Что я ищу, так это как мне создать "assignmentExpression" в коде, чтобы я мог скомпилировать его в setValue? Я полагаю, что это связано с Expression.Assign, но я просто не могу определить правильную комбинацию параметров для завершения кода.

В конечном итоге можно позвонить

Category category = *<get object from somewhere>*;
this.DoLambdaStuff(category, c => c.Name);

и это, в свою очередь, создаст геттер и сеттер для свойства "Name" объекта Category.

Версия выше компилируется, но когда я вызываю setValue (), это приводит к ArgumentException с «Выражение должно быть доступно для записи».

Еще раз спасибо.

Ответы [ 4 ]

5 голосов
/ 21 ноября 2013
void DoLambdaStuff<TObject, TValue>(TObject obj, Expression<Func<TObject, TValue>> expression) {

    Func<TObject, TValue> getValue = expression.Compile();
    TValue stuff = getValue(obj);

    var p = Expression.Parameter(typeof(TValue), "v");
    Expression<Action<TObject, TValue>> assignmentExpression = 
        Expression.Lambda<Action<TObject, TValue>>(Expression.Assign(expression.Body, p), expression.Parameters[0], p);

    Action<TObject, TValue> setValue = assignmentExpression.Compile();

    setValue(obj, stuff);
}
3 голосов
/ 16 мая 2010

Хорошо, код, который я ищу, выглядит примерно так ...

ParameterExpression objectParameterExpression = Expression.Parameter(
  typeof(TObject)),
  valueParameterExpression = Expression.Parameter(typeof(TValue)
);
Expression<Action<TObject, TValue>> setValueExpression = Expression.Lambda<Action<TObject, TValue>>(
  Expression.Block(
    Expression.Assign(
      Expression.Property(
        objectParameterExpression,
        ((MemberExpression) expression.Body).Member.Name
      ),
      valueParameterExpression
    )
  ),
  objectParameterExpression,
  valueParameterExpression
);
Action<TObject, TValue> setValue = setValueExpression.Compile();

Этот код работает, но только для мелких свойств (то есть свойств непосредственного объекта), но не работает для глубоких свойств - хотя функция получения может работать для глубоких свойств. Было бы интересно узнать, может ли кто-нибудь помочь мне изменить это для работы с глубокими свойствами, но я подниму это как отдельный вопрос.

2 голосов
/ 08 ноября 2011

Как заметил Мартин выше, «глубокие» свойства нарушают его решение. Причина, по которой это не работает, заключается в следующем выражении:

Expression.Property(
    objectParameterExpression
,  ((MemberExpression)expression.Body).Member.Name
)

Причина этого не сразу очевидна: производный класс объявляет свое собственное свойство getter, которое перенаправляет в базовый класс, но не определяет соответствующий установщик.

Чтобы обойти эту проблему, вам нужно найти свойство с помощью отражения, найти его объявленный тип, а затем получить соответствующее свойство от объявления. В отличие от свойства, которое вы получаете в производном классе, свойство от объявления имеет геттер и сеттер и поэтому может быть назначено. (Это предполагает, что установщик свойства объявлен вообще: если объявитель не предоставляет установщик, то никто другой не сможет его предоставить.)

Вот скелетная реализация, которая решает эту проблему. Замените вышеуказанный вызов на Expression.Property на вызов GetPropertyOrField ниже, и решение Мартина будет работать.

private static MemberExpression GetPropertyOrField(Expression baseExpr, string name) {
    if (baseExpr == null) {
        throw new ArgumentNullException("baseExpr");
    }
    if (string.IsNullOrWhiteSpace(name)) {
        throw new ArgumentException("name");
    }
    var type = baseExpr.Type;
    var properties = type
        .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
        .Where(p => p.Name.Equals(name))
        .ToArray();
    if (properties.Length == 1) {
        var res = properties[0];
        if (res.DeclaringType != type) {
            // Here is the core of the fix:
            var tmp = res
                .DeclaringType
                .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
                .Where(p => p.Name.Equals(name))
                .ToArray();
            if (tmp.Length == 1) {
                return Expression.Property(baseExpr, tmp[0]);
            }
        }
        return Expression.Property(baseExpr, res);
    }
    if (properties.Length != 0) {
        throw new NotSupportedException(name);
    }
    var fields = type
        .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
        .Where(p => p.Name.Equals(name))
        .ToArray();
    if (fields.Length == 1) {
        return Expression.Field(baseExpr, fields[0]);
    }
    if (fields.Length != 0) {
        throw new NotSupportedException(name);
    }
    throw new ArgumentException(
        string.Format(
            "Type [{0}] does not define property/field called [{1}]"
        ,   type
        ,   name
        )
    );
}
0 голосов
/ 15 мая 2010

Это должно быть возможно при использовании деревьев выражений , которые могут быть созданы из лямбда-выражений, изменены, а затем скомпилированы в делегат.

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