Модифицированная версия. Этот класс, вероятно, лучше, чем многие другие, которые вы можете найти вокруг :-) Это потому, что эта версия поддерживает прямые свойства (p => p.B
) (как и все остальные :-)), вложенные свойства (p => p.B.C.D
), поля (оба " терминал "и" в середине ", поэтому в p => p.B.C.D
оба поля B
и D
могут быть полями) и" внутреннее "приведение типов (так что p => ((BType)p.B).C.D
и p => (p.B as BType).C.D)
. Единственное, что не поддерживается приведение элемента "терминал" (поэтому нет p => (object)p.B
).
В генераторе есть два "кодовых пути": для простых выражений (p => p.B
) и для "вложенных" выражений. Существуют варианты кода для .NET 4.0 (с типом выражения Expression.Assign
). Из некоторых моих тестов самые быстрые делегаты: «простой» Delegate.CreateDelegate
для свойств, Expression.Assign
для полей и «простой» FieldSetter
для полей (этот немного медленнее, чем Expression.Assign
для полей). Таким образом, в .NET 4.0 вы должны убрать весь код, помеченный как 3.5.
Часть кода не моя. Первоначальная (простая) версия была основана на коде Fluent NHibernate (но он поддерживал только прямые свойства), некоторые другие части основаны на коде из Как установить значение поля в дереве выражений C #? и Назначение в деревьях выражений .NET 3.5 .
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 /247619/kak-ustanovit-znachenie-polya-v-dereve-vyrazhenii-c#247624
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
}