创建一个Action <T>来"设置"一个属性,当我为"get"提供LINQ表达式时

Ale*_*lex 14 c# expression-trees

我希望能够生成一个已编译的表达式来设置属性,给定lambda表达式为属性提供"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.
}
Run Code Online (Sandbox Code Playgroud)

我仍然试图理解各种类型的表达式类,所以如果你能指出我正确的方向,这将是伟大的.

Ada*_*cer 13

使用@ 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();
}
Run Code Online (Sandbox Code Playgroud)

您应该缓存已编译的表达式,以便将其用于多种用途.


Ani*_*Ani 7

你当然可以走表达式树然后Delegate.CreateDelegate用来创建合适的树Action<,>.这很简单,除了所有的验证检查(我不确定我是否涵盖了所有内容):

我不是表达式树专家,但我不认为 构建表达式树然后调用Compile是可能的,因为表达式树不能包含赋值语句,据我所知.(编辑:显然,这些已经在.NET 4中添加.这是一个难以发现的功能,因为C#编译器似乎无法从lambdas构建它们).

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());
}
Run Code Online (Sandbox Code Playgroud)

用法:

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     
}
Run Code Online (Sandbox Code Playgroud)

请注意,这会创建一个打开的实例委托,这意味着它没有绑定到任何特定的实例TContaining.修改它以绑定到特定实例很简单; 你必须传递一个TContaining方法,然后使用不同的重载Delegate.CreateDelegate.该方法的签名将类似于:

public static Action<TProperty> CreateSetter<TContaining, TProperty>
        (Expression<Func<TContaining, TProperty>> getter, TContaining obj)
Run Code Online (Sandbox Code Playgroud)

  • 不确定,但我认为他们在.net 4中添加了分配.但是setter应该已经有正确的签名,所以没有必要. (2认同)
  • 是的,作业是4.0 - 但你真的不需要它; Delegate.CreateDelegate是理想的.+1 (2认同)