Создайте действие <T>, чтобы "установить" свойство, когда мне предоставляется выражение LINQ для "get",

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

Вот что я ищу:

public Action<int> CreateSetter<T>(Expression<Func<T, int>> getter)
{
    // returns a compiled action using the details of the getter expression tree, or null
    // if the write property is not defined.
}

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

Ответы

Ответ 1

Используя @Ani ответ в качестве отправной точки, вы можете использовать следующее для создания скомпилированного выражения.

[TestMethod]
public void CreateSetterFromGetter()
{
    Action<Person, int> ageSetter = InitializeSet((Person p) => p.Age);
    Action<Person, string> nameSetter = InitializeSet((Person p) => p.Name);

    Person p1 = new Person();
    ageSetter(p1, 29);
    nameSetter(p1, "John");

    Assert.IsTrue(p1.Name == "John");
    Assert.IsTrue(p1.Age == 29);
}

public class Person { public int Age { get; set; } public string Name { get; set; } }

public static Action<TContainer, TProperty> InitializeSet<TContainer, TProperty>(Expression<Func<TContainer, TProperty>> getter)
{
    PropertyInfo propertyInfo = (getter.Body as MemberExpression).Member as PropertyInfo;

    ParameterExpression instance = Expression.Parameter(typeof(TContainer), "instance");
    ParameterExpression parameter = Expression.Parameter(typeof(TProperty), "param");

    return Expression.Lambda<Action<TContainer, TProperty>>(
        Expression.Call(instance, propertyInfo.GetSetMethod(), parameter),
        new ParameterExpression[] { instance, parameter }).Compile();
}

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

Ответ 2

Вы можете, конечно, пройти дерево выражений, а затем использовать Delegate.CreateDelegate для создания соответствующего Action<,>. Это довольно просто, за исключением всех проверок проверки (я не уверен, охватил ли я все):

Я не эксперт дерева выражений, но я не думаю, что создание дерева выражений, а затем вызов Compile возможен здесь, поскольку деревья выражений не могут содержать операторов присваивания, насколько я знаете. ( РЕДАКТИРОВАТЬ. По-видимому, они были добавлены в .NET 4. Это труднодоступная функция, поскольку компилятор С#, похоже, не может построить их из лямбды).

public static Action<TContaining, TProperty>
    CreateSetter<TContaining, TProperty>
    (Expression<Func<TContaining, TProperty>> getter)
{
    if (getter == null)
        throw new ArgumentNullException("getter");

    var memberEx = getter.Body as MemberExpression;

    if (memberEx == null)
        throw new ArgumentException("Body is not a member-expression.");

    var property = memberEx.Member as PropertyInfo;

    if (property == null)
        throw new ArgumentException("Member is not a property.");

    if(!property.CanWrite)
        throw new ArgumentException("Property is not writable.");

    return (Action<TContaining, TProperty>)
           Delegate.CreateDelegate(typeof(Action<TContaining, TProperty>),
                                   property.GetSetMethod());
}

Использование

public class Person { public int Age { get; set; } }

...

static void Main(string[] args)
{
    var setter = CreateSetter((Person p) => p.Age);
    var person = new Person();
    setter(person, 25);

    Console.WriteLine(person.Age); // 25     
}

Обратите внимание, что это создает делегат открытого экземпляра, что означает, что он не привязан к какому-либо конкретному экземпляру TContaining. Просто изменить его, чтобы привязываться к конкретному экземпляру; вам придется передать метод TContaining, а затем использовать другую перегрузку Delegate.CreateDelegate. Подпись метода будет выглядеть примерно так:

public static Action<TProperty> CreateSetter<TContaining, TProperty>
        (Expression<Func<TContaining, TProperty>> getter, TContaining obj)

Ответ 3

Указатели только я боюсь (я не на компьютере) - но;

  • lambda.Body скорее всего будет MemberExpression
  • сделать безопасный листинг (as и т.д.) и получить доступ к .Member
  • так как вы верите в это свойство, это должно быть PropertyInfo, поэтому test/cast и т.д.
  • из PropertyInfo, вызовите GetSetMethod(), чтобы получить соответствующий MethodInfo
  • используйте Delegate.CreateDelegate, чтобы получить это как делегат (передавая тип действия)
  • наконец, отбросить делегат на ожидаемый тип делегирования