Создание делегата для установки свойства - PullRequest
33 голосов
/ 13 мая 2010

Я создал методы для преобразования лямбда-свойства в делегат:

public static Delegate MakeGetter<T>(Expression<Func<T>> propertyLambda)
{
    var result = Expression.Lambda(propertyLambda.Body).Compile();
    return result;
}

public static Delegate MakeSetter<T>(Expression<Action<T>> propertyLambda)
{
    var result = Expression.Lambda(propertyLambda.Body).Compile();
    return result;
}

Эти работы:

Delegate getter = MakeGetter(() => SomeClass.SomeProperty);
object o = getter.DynamicInvoke();

Delegate getter = MakeGetter(() => someObject.SomeProperty);
object o = getter.DynamicInvoke();

но они не скомпилируются:

Delegate setter = MakeSetter(() => SomeClass.SomeProperty);
setter.DynamicInvoke(new object[]{propValue});

Delegate setter = MakeSetter(() => someObject.SomeProperty);
setter.DynamicInvoke(new object[]{propValue});

Строки MakeSetter завершаются с ошибкой «Аргументы типа не могут быть выведены из использования. Попробуйте явно указать аргументы типа.»

Возможно ли то, что я пытаюсь сделать? Заранее спасибо.

Ответы [ 4 ]

53 голосов
/ 13 мая 2010

API Expression поддерживает это в .NET 4.0, но, к сожалению, компилятор C # не добавляет никаких дополнительных конфет для поддержки. Но хорошая новость заключается в том, что вы можете просто взять выражение «get» (которое компилятор C # может записать) и переписать его как выражение «set».

И даже лучше; если у вас нет .NET 4.0, все еще есть по крайней мере два других способа выполнить "set" с помощью выражения, записанного как "get".

Вот они все, для информации:

using System;
using System.Linq.Expressions;
using System.Reflection;
class Foo {
    public string Bar { get; set; }
    static void Main() {
        // take a "get" from C#
        Expression<Func<Foo, string>> get = foo => foo.Bar;

        // re-write in .NET 4.0 as a "set"
        var member = (MemberExpression)get.Body;
        var param = Expression.Parameter(typeof(string), "value");
        var set = Expression.Lambda<Action<Foo, string>>(
            Expression.Assign(member, param), get.Parameters[0], param);

        // compile it
        var action = set.Compile();
        var inst = new Foo();
        action(inst, "abc");
        Console.WriteLine(inst.Bar); // show it working

        //==== reflection
        MethodInfo setMethod = ((PropertyInfo)member.Member).GetSetMethod();
        setMethod.Invoke(inst, new object[] { "def" });
        Console.WriteLine(inst.Bar); // show it working

        //==== Delegate.CreateDelegate
        action = (Action<Foo, string>)
            Delegate.CreateDelegate(typeof(Action<Foo, string>), setMethod);
        action(inst, "ghi");
        Console.WriteLine(inst.Bar); // show it working
    }
}
17 голосов
/ 21 октября 2015

Согласно моим комментариям - потому что ссылки не работают - я разместил полный код в качестве ответа на вопрос ДА, возможно сделать то, что запрашивает ОП . и вот симпатичная маленькая жемчужина Ника, демонстрирующая это. Ник отмечает эту страницу и другую страницу за его полное решение наряду с показателями производительности. Я предоставляю это ниже вместо просто ссылку .

// returns property getter
public static Func<TObject, TProperty> GetPropGetter<TObject, TProperty>(string propertyName)
{
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject), "value");

    Expression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

    Func<TObject, TProperty> result =
        Expression.Lambda<Func<TObject, TProperty>>(propertyGetterExpression, paramExpression).Compile();

    return result;
}

// returns property setter:
public static Action<TObject, TProperty> GetPropSetter<TObject, TProperty>(string propertyName)
{            
    ParameterExpression paramExpression = Expression.Parameter(typeof(TObject));

    ParameterExpression paramExpression2 = Expression.Parameter(typeof(TProperty), propertyName);

    MemberExpression propertyGetterExpression = Expression.Property(paramExpression, propertyName);

    Action<TObject, TProperty> result = Expression.Lambda<Action<TObject, TProperty>>
    (
        Expression.Assign(propertyGetterExpression, paramExpression2), paramExpression, paramExpression2
    ).Compile();

    return result;
}
4 голосов
/ 13 мая 2010

Ваш MakeSetter ожидает Action<T>, а вы передаете ему Func<T> (() => someObject.SomeProperty).Попробуйте следующее:

Delegate setter = MakeSetter((prop) => {someObject.SomeProperty = prop;});
setter.DynamicInvoke(new object[]{propValue});

РЕДАКТИРОВАТЬ Не похоже, что вы можете преобразовать лямбда-выражения в выражения .Это несколько крутой способ сделать это без выражений - прямо для делегатов:

class Test2 {
    delegate void Setter<T>(T value);

    public static void Test() {
        var someObject = new SomeObject();
        Setter<string> setter = (v) => { t.SomeProperty = v; };
        setter.DynamicInvoke(new object[]{propValue});
    }
}
4 голосов
/ 13 мая 2010

Action<T> представляет делегата, который принимает один параметр типа T и ничего не возвращает. Лямбда-выражения, которые вы предоставляете MakeSetter, представляют делегатов, которые не принимают параметров и возвращают либо SomeClass.SomeProperty, либо someObject.SomeProperty.

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

...