如何设置属性选择器的值 Expression<Func<T,TResult>>

Posted

技术标签:

【中文标题】如何设置属性选择器的值 Expression<Func<T,TResult>>【英文标题】:How set value a property selector Expression<Func<T,TResult>> 【发布时间】:2011-12-27 18:33:11 【问题描述】:

我需要使用模式工厂的想法将我的 Person 类实体中的实体属性 Address 与我的 FactoryEntities 类中的表达式 linq 相关联,看看这就是我所拥有的并且我想要做的:

Address address = new Address();
address.Country = "Chile";
address.City = "Santiago";
address.ZipCode = "43532";
//Factory instance creation object
//This is idea
Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address);

public class Person: Entity

    public string Name get; set; 
    public string LastName get; set; 
    public Address Address get; set; 


public class Address: Entity

    public string Country get; set; 
    public string City get; set; 
    public string ZipCode get; set; 


public class FactoryEntity<TEntity> where TEntity : Entity

    public void AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> entityExpression, TProperty newValueEntity) where TProperty : Entity
    
        if (instanceEntity == null || instanceEntity.IsTransient())
            throw new ArgumentNullException();

        /*TODO: Logic the association and validation 
        How set the newValueEntity into the property of entityExpression (x=>x.Direccion = direccion*/
    

【问题讨论】:

您拥有的propertyInfoTEntity,而不是TProperty。您不能使用它来访问不同类型对象的属性。协会应该走哪条路?你在这里尝试做的事情对我来说没有意义。 这是一个非常有漏洞的代码,不容易说出你想要的,请澄清它。 很抱歉解释不清楚,但我想做的是占用一个工厂,允许我将对象关联到拦截验证关联,以便正确检查 Property selector Expression<Func<T>>. How to get/set value to selected property的可能重复 【参考方案1】:

这行得通:

以下帮助方法将 getter 表达式转换为 setter 委托。如果您想返回 Expression&lt;Action&lt;T,TProperty&gt;&gt; 而不是 Action&lt;T,TProperty&gt;,请不要在最后调用 Compile() 方法。

注意:代码来自 Ian Mercer 的博客:http://blog.abodit.com/2011/09/convert-a-property-getter-to-a-setter/

    /// <summary>
    /// Convert a lambda expression for a getter into a setter
    /// </summary>
    public static Action<T, TProperty> GetSetter<T, TProperty>(Expression<Func<T, TProperty>> expression)
    
        var memberExpression = (MemberExpression)expression.Body;
        var property = (PropertyInfo)memberExpression.Member;
        var setMethod = property.GetSetMethod();

        var parameterT = Expression.Parameter(typeof(T), "x");
        var parameterTProperty = Expression.Parameter(typeof(TProperty), "y");

        var newExpression =
            Expression.Lambda<Action<T, TProperty>>(
                Expression.Call(parameterT, setMethod, parameterTProperty),
                parameterT,
                parameterTProperty
            );

        return newExpression.Compile();
    

【讨论】:

这个方法怎么称呼?我似乎无法为委托提供正确的参数。 @hbob ,类似于:GetSetter( (string example) =&gt; example.Length ) 不知道TProperty怎么办?使用 Expression.Lambda> 不起作用 :( @markmnl ***.com/questions/729295/… 可能会有所帮助 @DaveCousineau 也许,你检查过 IL 的这个答案和你的答案吗?我知道这个答案已经很老了,Expression.Assign 出现在 .NET 4.0 中,而 @IanMercer 的代码与 .NET 3.5 兼容。我不确定实际执行的内容是否有所不同 - 我们需要对其进行测试【参考方案2】:

你可以这样设置属性:

public void AssociateWithEntity<TProperty>(
    Expression<Func<TEntity, TProperty>> entityExpression,
    TProperty newValueEntity)
    where TProperty : Entity

    if (instanceEntity == null)
        throw new ArgumentNullException();

    var memberExpression = (MemberExpression)entityExpression.Body;
    var property = (PropertyInfo)memberExpression.Member;

    property.SetValue(instanceEntity, newValueEntity, null);

这仅适用于属性,不适用于字段,尽管添加对字段的支持应该很容易。

但是您获取此人的代码不起作用。如果你想保留void 的返回类型AssociateWithEntity(),你可以这样做:

var factory = new FactoryEntity<Person>();
factory.AssociateWithEntity(p => p.Address, address);
Person person = factory.InstanceEntity;

另一种选择是流畅的界面:

Person person = new FactoryEntity<Person>()
    .AssociateWithEntity(p => p.Address, address)
    .InstanceEntity;

【讨论】:

【参考方案3】:

另一个解决方案是获取属性所有者并使用反射调用属性设置器。这种方案的优点是不使用扩展方法,可以任意类型调用。

private void SetPropertyValue(Expression<Func<object, object>> lambda, object value)

  var memberExpression = (MemberExpression)lambda.Body;
  var propertyInfo = (PropertyInfo)memberExpression.Member;
  var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
  var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();    
  propertyInfo.SetValue(propertyOwner, value, null);            

...
SetPropertyValue(s => myStuff.MyPropy, newValue);

【讨论】:

lambda.Body 可能是UnaryExpression,见***.com/questions/12420466/…【参考方案4】:

这是我使用Expression.Assign的解决方案,但仔细观察后,接受的答案同样好。

// optionally or additionally put in a class<T> to capture the object type once
// and then you don't have to repeat it if you have a lot of properties
public Action<T, TProperty> GetSetter<T, TProperty>(
   Expression<Func<T, TProperty>> pExpression
) 
   var parameter1 = Expression.Parameter(typeof(T));
   var parameter2 = Expression.Parameter(typeof(TProperty));

   // turning an expression body into a PropertyInfo is common enough
   // that it's a good idea to extract this to a reusable method
   var member = (MemberExpression)pExpression.Body;
   var propertyInfo = (PropertyInfo)member.Member;

   // use the PropertyInfo to make a property expression
   // for the first parameter (the object)
   var property = Expression.Property(parameter1, propertyInfo);

   // assignment expression that assigns the second parameter (value) to the property
   var assignment = Expression.Assign(property, parameter2);

   // then just build the lambda, which takes 2 parameters, and has the assignment
   // expression for its body
   var setter = Expression.Lambda<Action<T, TProperty>>(
      assignment,
      parameter1,
      parameter2
   );

   return setter.Compile();

您可以做的另一件事是封装它们:

public sealed class StrongProperty<TObject, TProperty> 
   readonly PropertyInfo mPropertyInfo;

   public string Name => mPropertyInfo.Name;
   public Func<TObject, TProperty> Get  get; 
   public Action<TObject, TProperty> Set  get; 
   // maybe other useful properties

   internal StrongProperty(
      PropertyInfo pPropertyInfo,
      Func<TObject, TProperty> pGet,
      Action<TObject, TProperty> pSet
   ) 
      mPropertyInfo = pPropertyInfo;
      Get = pGet;
      Set = pSet;
   

现在您可以传递这些,类似于委托,并编写其逻辑可以因属性而异的代码。这解决了您不能通过引用传递属性的事实。

【讨论】:

【参考方案5】:

就是这个想法,考虑到 svick 的贡献,我用这段代码为我工作:

public class FactoryEntity<TEntity> where TEntity : Entity, new()



private TEntity _Entity;

    public FactoryEntity()
    
        _Entity = new TEntity();
    

public TEntity Build()
    
        if (_Entity.IsValid())
            throw new Exception("_Entity.Id");

        return _Entity;
    

public FactoryEntity<TEntity> AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> foreignEntity, TProperty instanceEntity) where TProperty : Entity
    
        if (instanceEntity == null || instanceEntity.IsTransient())
            throw new ArgumentNullException();

        SetObjectValue<TEntity, TProperty>(_Entity, foreignEntity, instanceEntity);
        return this;
    

private void SetObjectValue<T, TResult>(object target, Expression<Func<T, TResult>> expression, TResult value)
    
        var memberExpression = (MemberExpression)expression.Body;
        var propertyInfo = (PropertyInfo)memberExpression.Member;
        var newValue = Convert.ChangeType(value, value.GetType());
        propertyInfo.SetValue(target, newValue, null);
    

这里我调用工厂为我构建一个有效的 Person 对象

Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address).Build();

但是我不知道这段代码是否最优,至少我没有调用compile()方法,在说什么?

谢谢

【讨论】:

【参考方案6】:

我已经混合了Rytis I 解决方案和https://***.com/a/12423256/254109

private static void SetPropertyValue<T>(Expression<Func<T>> lambda, object value)
    
        var memberExpression = (MemberExpression)lambda.Body;
        var propertyInfo = (PropertyInfo)memberExpression.Member;
        var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
        var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();

        propertyInfo.SetValue(propertyOwner, value, null);
    

然后叫它

SetPropertyValue(() => myStuff.MyProp, newValue);

【讨论】:

这里是嵌套表达式(多个点)的 GetProperty 解决方案:codeproject.com/Articles/733296/…【参考方案7】:

一切都简单得多:

public static Action<T, TValue> GetSetter<T, TValue>(
    Expression<Func<T, TValue>> expression)

    var parameter = Expression.Parameter(typeof(TValue), "value");
    var setterLambda = Expression.Lambda<Action<T, TValue>>(
        Expression.Assign(expression.Body, parameter),
        expression.Parameters[0],
        parameter);

    return setterLambda.Compile();

【讨论】:

以上是关于如何设置属性选择器的值 Expression<Func<T,TResult>>的主要内容,如果未能解决你的问题,请参考以下文章

如何增加jquery选择器的值

spring <aop:pointcut>标签 expression属性如何定义路径

hex value如何查颜色

如何在单击或更改时获取 $(this) 选择器的值

iPhone,如何在代码中将日期选择器的最小日期设置为今天?

vb.net - 如何将今天设置为时间选择器的默认日期?