属性选择器Expression <Func <T >>。如何获取/设置选定属性的值


71

我有一个想要以这种方式构造的对象:

var foo = new FancyObject(customer, c=>c.Email); //customer has Email property

我应该如何声明第二个参数?

访问所选属性设置器/获取器的代码将如何显示?

更新。模型中有几个具有Email属性的实体。因此,签名可能看起来像:

public FancyObject(Entity holder, Expression<Func<T>> selector)

和构造函数调用

var foo = new FancyObject(customer, ()=>customer.Email);

Answers:


115

参数将是Expression<Func<Customer,string>> selector。可以通过平面编译来读取它:

 Func<Customer,string> func = selector.Compile();

然后您就可以访问func(customer)。分配比较棘手;对于简单的选择器,您可能希望可以将其分解为:

var prop = (PropertyInfo)((MemberExpression)selector.Body).Member;
prop.SetValue(customer, newValue, null);

但是,更复杂的表达式要么需要手动遍历,要么需要某些4.0表达式节点类型:

        Expression<Func<Customer, string>> email
             = cust => cust.Email;

        var newValue = Expression.Parameter(email.Body.Type);
        var assign = Expression.Lambda<Action<Customer, string>>(
            Expression.Assign(email.Body, newValue),
            email.Parameters[0], newValue);

        var getter = email.Compile();
        var setter = assign.Compile();

谢谢Marc,我认为这已经足够了,就像Jon建议的那样,asumng类将具有{public class FancyObject <TSource,TResult>}的泛型类型。
v00d00

1
@Neo,不,那不是“更正确的”-只是“不太直接”;抱歉,但我对ExpressionAPI非常了解。我可以满足email.Parameters.Single(),但是:上面的版本很好。
马克·格雷夫

我正在尝试将您的代码与Expression.Lambda<Action<Trade, object>>(而不是string)一起使用,以便可以在各种类型的表达式对的集合上通用使用该代码。但是,当属性的类型为时,出现错误“类型为'System.String'的ParameterExpression无法用于类型为'System.Object'的委托参数” string。我想除了以外的其他任何类型也会发生这种情况object。任何想法如何使这种代码适用于这种情况?
2012年

@Neo,您将必须使用Expression.Convert,这是通用的类型转换
Marc Gravell

是的,我发现了这一点:stackoverflow.com/questions/10760139但是,我无法使它正常工作,所以我提出了一个问题:stackoverflow.com/questions/12821323 如果能看一看,将不胜感激... :)
2012年

6

看起来类型必须是带有两个类型参数的泛型-源和结果。例如,您可以使用:

var foo = new FancyObject<Customer, string>(customer, c => c.Email);

第一个参数的类型为TSource,第二个参数为Expression<Func<TSource, TResult>>

public class FancyObject<TSource, TResult>
{
    private readonly TSource value;
    private readonly Expression<Func<TSource, TResult>> projection;

    public FancyObject(TSource value, 
                       Expression<Func<TSource, TResult>> projection)
    {
        this.value = value;
        this.projection = projection;
    }
}

您可以简化非类类型的静态方法的使用:

var foo = FancyObject.Create(customer, c => c.Email);

可以使用类型推断来得出类型参数。


谢谢。以及如何访问所选属性?
v00d00

@jonskeet在这种情况下刚刚失去兴趣,将如何使用投影?我很感兴趣,因为我了解您可以仅使用Func来执行类似的操作,然后调用Func.Invoke()来获取值。
MrEdmundo

@MrEdmundo:嗯,您可以在LINQ to SQL中使用它,例如...在这里您不能使用Func。
乔恩·斯基特

1
@JonSkeet谢谢,在这种情况下,执行表达式的正确方法是什么(我是Expressions的新手)。
MrEdmundo

@MrEdmundo:说实话,这完全取决于您要使用的功能。对于我来说,这个问题太宽泛了。
乔恩·斯基特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.