method - generic delegates in c#
Cómo establecer el valor de una propiedad selector Expresión<Func<T, TResult>> (6)
Esto funciona:
El siguiente método auxiliar convierte una expresión de obtención en un delegado de establecimiento. Si desea devolver una Expression<Action<T,TProperty>>
lugar de una Action<T,TProperty>
, simplemente no llame al método Compile()
al final.
Nota: El código es del blog de 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();
}
Necesito asociar una propiedad de entidad Dirección en mi clase de persona entidad con expresiones linq en mi clase de FactoryEntities usando la idea de fábrica de patrones, mira esto es lo que tengo y quiero hacer:
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*/
}
}
Esa es la idea, trabajé para mí con este código, teniendo en cuenta la contribución de 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);
}
}
Aquí llamo a la fábrica para que construya el objeto Persona en un lugar válido.
Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address).Build();
Pero no sé si este código es óptimo o no, al menos no hago una llamada al método compile (), ¿qué están diciendo?
Gracias
Esta es mi solución que usa Expression.Assign
, pero después de mirar más de cerca, la respuesta aceptada es igual de buena.
// 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();
}
Otra cosa que puedes hacer es encapsularlos:
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;
}
}
Y ahora puede pasar esto, similar a los delegados, y escribir código cuya lógica puede variar según la propiedad. Esto evita el hecho de que no puede pasar propiedades por referencia.
He hecho la solución mixta y 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);
}
Y llamalo
SetPropertyValue(() => myStuff.MyProp, newValue);
Otra solución es conseguir que el propietario de la propiedad invoque el configurador de propiedades mediante la reflexión. La ventaja de esta solución es que no utiliza métodos de extensión y se puede llamar con cualquier tipo
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);
Puede establecer la propiedad de esta manera:
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);
}
Esto funcionará solo para las propiedades, no para los campos, aunque agregar el soporte para los campos debería ser fácil.
Pero el código que tienes para conseguir que la persona no funcione. Si desea mantener el tipo de retorno void
de AssociateWithEntity()
, puede hacerlo así:
var factory = new FactoryEntity<Person>();
factory.AssociateWithEntity(p => p.Address, address);
Person person = factory.InstanceEntity;
Otra opción es una interfaz fluida:
Person person = new FactoryEntity<Person>()
.AssociateWithEntity(p => p.Address, address)
.InstanceEntity;