How set value a property selector Expression

2020-01-27 03:20发布

问题:

i need associate a entity property Address in my Person class entity with expressions linq in my FactoryEntities class using pattern factory idea, look this is what I have and I want to do:

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

回答1:

This works:

The following helper method converts a getter expression into a setter delegate. If you want to return an Expression<Action<T,TProperty>> instead of an Action<T,TProperty>, just don't call the Compile() method at the end.

Note: The code is from Ian Mercer's blog: 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:

You can set the property like this:

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

This will work only for properties, not fields, although adding support for fields should be easy.

But the code you have for getting the person won't work. If you want to keep the void return type of AssociateWithEntity(), you could do it like this:

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

Another option is a fluent interface:

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


回答3:

Another solution is to get property owner ant invoke property setter using reflection. The advantage of this solution that it does not use extension methods and can be call with any type

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


回答4:

This is my solution that uses Expression.Assign, but after looking more closely, the accepted answer is just as good.

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

Another thing you can do is encapsulate them:

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

And now you can pass these around, similar to delegates, and write code whose logic can vary by property. This gets around the fact that you can't pass properties by reference.



回答5:

That's the idea, i'm worked for me with this code, taking into account the contribution of 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);
    }
}

Here I call the factory for me to build the Person object in a valid

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

But I do not know if this code is optimal or not, at least I do not make a call to compile() method, what are saying?

thanks



回答6:

I've made mixed Rytis I solution and https://stackoverflow.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);
    }

And call it

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