Versione modificata. Questa classe è probabilmente migliore di molte altre che puoi trovare in giro :-) Questo perché questa versione supporta le proprietà dirette (p => p.B
) (come tutti gli altri :-)), le proprietà nidificate (p => p.B.C.D
), i campi (sia "terminal" che " nel mezzo ", quindi in p => p.B.C.D
sia B
sia D
potrebbero essere campi) e tipo" interno "di tipi (quindi p => ((BType)p.B).C.D
e p => (p.B as BType).C.D)
. L'unica cosa che non è supportata è la trasmissione dell'elemento" terminale "(quindi non p => (object)p.B
) .
ci sono due "codepaths" nel generatore:. per le espressioni semplici (p => p.B
) e per le espressioni "nidificate" ci sono varianti per codice .NET 4.0 (che ha il tipo Expression.Assign
espressione) Da alcuni parametri di riferimento. I miei delegati più veloci sono: "semplice" Delegate.CreateDelegate
per le proprietà, Expression.Assign
per i campi e "semplice" FieldSetter
per i campi (questo è solo un po 'più lento di Expression.Assign
per i campi). Quindi sotto .NET 4.0 si dovrebbe togliere tutto il codice contrassegnato come 3.5.
Parte del codice non è mia. La versione iniziale (semplice) era basata sul codice Fluent NHibernate (ma supportava solo le proprietà dirette), altre parti si basano sul codice da How do I set a field value in an C# Expression tree? e Assignment in .NET 3.5 expression trees.
public static class FluentTools
{
public static Action<T, TValue> GetterToSetter<T, TValue>(Expression<Func<T, TValue>> getter)
{
ParameterExpression parameter;
Expression instance;
MemberExpression propertyOrField;
GetMemberExpression(getter, out parameter, out instance, out propertyOrField);
// Very simple case: p => p.Property or p => p.Field
if (parameter == instance)
{
if (propertyOrField.Member.MemberType == MemberTypes.Property)
{
// This is FASTER than Expression trees! (5x on my benchmarks) but works only on properties
PropertyInfo property = propertyOrField.Member as PropertyInfo;
MethodInfo setter = property.GetSetMethod();
var action = (Action<T, TValue>)Delegate.CreateDelegate(typeof(Action<T, TValue>), setter);
return action;
}
#region .NET 3.5
else // if (propertyOrField.Member.MemberType == MemberTypes.Field)
{
// 1.2x slower than 4.0 method, 5x faster than 3.5 method
FieldInfo field = propertyOrField.Member as FieldInfo;
var action = FieldSetter<T, TValue>(field);
return action;
}
#endregion
}
ParameterExpression value = Expression.Parameter(typeof(TValue), "val");
Expression expr = null;
#region .NET 3.5
if (propertyOrField.Member.MemberType == MemberTypes.Property)
{
PropertyInfo property = propertyOrField.Member as PropertyInfo;
MethodInfo setter = property.GetSetMethod();
expr = Expression.Call(instance, setter, value);
}
else // if (propertyOrField.Member.MemberType == MemberTypes.Field)
{
expr = FieldSetter(propertyOrField, value);
}
#endregion
//#region .NET 4.0
//// For field access it's 5x faster than the 3.5 method and 1.2x than "simple" method. For property access nearly same speed (1.1x faster).
//expr = Expression.Assign(propertyOrField, value);
//#endregion
return Expression.Lambda<Action<T, TValue>>(expr, parameter, value).Compile();
}
private static void GetMemberExpression<T, U>(Expression<Func<T, U>> expression, out ParameterExpression parameter, out Expression instance, out MemberExpression propertyOrField)
{
Expression current = expression.Body;
while (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs)
{
current = (current as UnaryExpression).Operand;
}
if (current.NodeType != ExpressionType.MemberAccess)
{
throw new ArgumentException();
}
propertyOrField = current as MemberExpression;
current = propertyOrField.Expression;
instance = current;
while (current.NodeType != ExpressionType.Parameter)
{
if (current.NodeType == ExpressionType.Convert || current.NodeType == ExpressionType.TypeAs)
{
current = (current as UnaryExpression).Operand;
}
else if (current.NodeType == ExpressionType.MemberAccess)
{
current = (current as MemberExpression).Expression;
}
else
{
throw new ArgumentException();
}
}
parameter = current as ParameterExpression;
}
#region .NET 3.5
// Based on https://stackoverflow.com/questions/321650/how-do-i-set-a-field-value-in-an-c-expression-tree/321686#321686
private static Action<T, TValue> FieldSetter<T, TValue>(FieldInfo field)
{
DynamicMethod m = new DynamicMethod("setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(FluentTools));
ILGenerator cg = m.GetILGenerator();
// arg0.<field> = arg1
cg.Emit(OpCodes.Ldarg_0);
cg.Emit(OpCodes.Ldarg_1);
cg.Emit(OpCodes.Stfld, field);
cg.Emit(OpCodes.Ret);
return (Action<T, TValue>)m.CreateDelegate(typeof(Action<T, TValue>));
}
// Based on https://stackoverflow.com/questions/208969/assignment-in-net-3-5-expression-trees/3972359#3972359
private static Expression FieldSetter(Expression left, Expression right)
{
return
Expression.Call(
null,
typeof(FluentTools)
.GetMethod("AssignTo", BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(left.Type),
left,
right);
}
private static void AssignTo<T>(ref T left, T right) // note the 'ref', which is
{ // important when assigning
left = right; // to value types!
}
#endregion
}
Cosa dovrebbe accadere se non c'è getter (cioè la proprietà ha solo un setter)? – Joey
Non sto creando una soluzione generale qui. Le proprietà che ho intenzione di utilizzare su questo avranno getter e setter accessibili, mi sto solo chiedendo come costruire il Setter –
Dai uno sguardo alla prima risposta da questa domanda. http://stackoverflow.com/questions/2823236/creating-a-property-setter-delegate – baalazamon