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

32

我需要在我的Person类实体中使用LINQ表达式将一个实体属性Address与FactoryEntities类关联起来,使用工厂模式的思路。这是我所拥有的,我想要做的事情:

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*/
    }
}

你那里的 propertyInfo 是针对 TEntity 而不是 TProperty 的。你不能使用它来访问不同类型对象的属性。关联应该是哪个方向?我觉得你在这里尝试做的事情没有意义。 - Jeff Mercado
这是非常有缺陷的代码,不容易理解你想要什么,请澄清一下。 - Saeed Amiri
很抱歉,之前的解释可能不够清晰。我的意思是想要占用一个工厂,使我能够关联对象并拦截验证关联,以便正确检查。 - Emilio Montes
8个回答

33

下面的代码可以正常工作:

以下这个帮助方法将getter表达式转换为setter委托。如果您想返回一个 Expression<Action<T,TProperty>> 而不是 Action<T,TProperty>,只需不调用最后的 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();
    }

2
@hbob,类似这样:GetSetter((string example) => example.Length) - smartcaveman
1
如果您不了解TProperty怎么办?使用Expression.Lambda<Action<T, object>>是行不通的 :( - markmnl
@markmnl 我有同样的问题,你找到解决方案了吗? - S. Robijns
如果您想在lambda表达式中使用对象参数,但签名需要更具体的内容,则需要将对象类型的参数表达式转换为所需的特定类型,以使调用表达式合法。 您需要的工厂方法是Expression.Convert。@S.Robijns @markmnl - smartcaveman
1
@DaveCousineau,也许你可以查看一下这个答案和你的代码的IL代码。我知道这个答案已经很久了,而Expression.Assign是在.NET 4.0中引入的,而@IanMercer的代码兼容于.NET 3.5。但我不确定执行的实际差异-我们需要进行测试。 - smartcaveman
显示剩余5条评论

6
您可以这样设置属性:
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);
}

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

但是你现在获取人的代码是无效的。如果你想保持AssociateWithEntity()void返回类型,你可以这样做:

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;

4

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

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);

1
lambda.Body 可能是 UnaryExpression,请参见 https://dev59.com/_2ct5IYBdhLWcg3wAY3q - xmedeko

0

这就是目的,我用这段代码达到了效果,在此感谢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()方法,你有什么说的?谢谢。

0

我已经制作了混合 Rytis I 解决方案和 https://dev59.com/_2ct5IYBdhLWcg3wAY3q#12423256

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解决方案:https://www.codeproject.com/Articles/733296/Expression-Parsing-and-Nested-Properties - baHI

0
这是我的解决方案,使用了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;
   }
}

现在你可以像传递委托一样传递它们,并编写其逻辑因属性而异的代码。这避免了无法通过引用传递属性的问题。

0

一切都变得更加简单:

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();
}

你会如何使用这个扩展方法? - kkuilla

0
使用ExpressionVisitor来遍历嵌套项的树形结构。
var t = new SauceObject();

t.UpdateValueAndTrack(t => t.NestedProperty.Field1, "newvalue");

Console.WriteLine(t.NestedProperty.Field1);

public static void UpdateValueAndTrack<TSourceObject, TSetValue>(this TSourceObject sourceObject, Expression<Func<TSourceObject, TSetValue>> sourceExpression, TSetValue sourceValue)
{
    var t = new UpdateValueVisitor(sourceValue);
    var modifiedExpression = (Expression<Func<TSourceObject, TSetValue>>)t.Visit(sourceExpression);

    // Compile and execute the modified expression
    var modifiedFunc = modifiedExpression.Compile();
    modifiedFunc(sourceObject);
}    
public class SauceObject
{
public NestedProperty NestedProperty { get; set; } = new NestedProperty();
}

public class NestedProperty
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}


public class UpdateValueVisitor : ExpressionVisitor
{
private readonly object newValue;
private bool foundMember = false;

public UpdateValueVisitor(object newValue)
{
    this.newValue = newValue;
}

protected override Expression VisitMember(MemberExpression node)
{
    if (!foundMember && node.Member is System.Reflection.PropertyInfo propertyInfo)
    {
        // Update property
        var newValueExpression = Expression.Constant(newValue, propertyInfo.PropertyType);
        var assignment = Expression.Assign(node, newValueExpression);
        foundMember = true;
        return assignment;
    }

    return base.VisitMember(node);
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接