Expression< Func< TModel, string>比;Expression< Action< TMo
本文关键字:Expression Action TMo Func lt TModel string | 更新日期: 2023-09-27 18:11:08
我是新的表达式,我想知道如果它是如何以任何可能的方式转换我的表达式
让我们说,在这个例子中,我的TModel的类型是Customer,并将它分配到这样的地方:
Expression<Func<TModel, string>> getvalueexpression = customer =>customer.Name
变成
Expression<Action<TModel,string>> setvalueexpression = [PSEUDOCODE] getvalueexpression = input
Action<TModel,string> Setter = setvalueexpression.Compile();
Setter(mycustomer,value);
所以简而言之,我想以某种方式构建和编译一个表达式,它将我的getter表达式指定的客户名称设置为一个特定的值。
修改版本。这个类可能比你能找到的许多其他类更好:-)这是因为这个版本支持直接属性(p => p.B
)(和其他人一样:-)),嵌套属性(p => p.B.C.D
),字段("终端"answers"在中间",所以在p => p.B.C.D
中B
和D
都可以是字段)和类型的"内部"转换(所以p => ((BType)p.B).C.D
和p => (p.B as BType).C.D)
。唯一不支持的是"terminal"元素的强制转换(所以没有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#表达式树中设置字段值的代码?
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
}
static Expression<Action<T, TProperty>> MakeSetter<T, TProperty>(Expression<Func<T, TProperty>> getter)
{
var memberExpr = (MemberExpression)getter.Body;
var @this = Expression.Parameter(typeof(T), "$this");
var value = Expression.Parameter(typeof(TProperty), "value");
return Expression.Lambda<Action<T, TProperty>>(
Expression.Assign(Expression.MakeMemberAccess(@this, memberExpr.Member), value),
@this, value);
}
我有这个辅助方法,它返回属性的属性信息:
public static PropertyInfo GetPropertyInfo<T, U>(Expression<Func<T, U>> property) where T : class
{
var memberExpression = (property.Body as MemberExpression);
if (memberExpression != null && memberExpression.Member is PropertyInfo)
{
return memberExpression.Member as PropertyInfo;
}
throw new InvalidOperationException("Invalid usage of GetPropertyInfo");
}
用法:GetPropertyInfo((MyClass c) => c.PropertyName);
你可以使用PropertyInfo来设置一个类的属性值。
您将需要修改代码以满足您的需求,但希望它会有所帮助。
由于正确的答案对我不起作用(表达式中的集合),但将我推向了正确的方向,我需要对此进行大量调查,我认为我提出了一个可以为任何成员表达式生成setter的方法。
对于属性和字段,它的行为与标记的答案相同(我相信它更加透明)。
它有对列表和字典的额外支持-请参阅评论。
public static Action<TObject, TPropertyOnObject> GetSetter<TObject, TPropertyOnObject>(Expression<Func<TObject, TPropertyOnObject>> getterExpression)
{
/*** SIMPLE PROPERTIES AND FIELDS ***/
// check if the getter expression refers directly to a PROPERTY or FIELD
var memberAcessExpression = getterExpression.Body as MemberExpression;
if (memberAcessExpression != null)
{
//to here we assign the SetValue method of a property or field
Action<object, object> propertyOrFieldSetValue = null;
// property
var propertyInfo = memberAcessExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => propertyInfo.SetValue(declaringObjectInstance, propertyOrFieldValue);
};
// field
var fieldInfo = memberAcessExpression.Member as FieldInfo;
if (fieldInfo != null)
{
propertyOrFieldSetValue = (declaringObjectInstance, propertyOrFieldValue) => fieldInfo.SetValue(declaringObjectInstance, propertyOrFieldValue);
}
// This is the expression to get declaring object instance.
// Example: for expression "o=>o.Property1.Property2.CollectionProperty[3].TargetProperty" it gives us the "o.Property1.Property2.CollectionProperty[3]" part
var memberAcessExpressionCompiledLambda = Expression.Lambda(memberAcessExpression.Expression, getterExpression.Parameters.Single()).Compile();
Action<TObject, TPropertyOnObject> setter = (expressionParameter, value) =>
{
// get the object instance on which is the property we want to set
var declaringObjectInstance = memberAcessExpressionCompiledLambda.DynamicInvoke(expressionParameter);
Debug.Assert(propertyOrFieldSetValue != null, "propertyOrFieldSetValue != null");
// set the value of the property
propertyOrFieldSetValue(declaringObjectInstance, value);
};
return setter;
}
/*** COLLECTIONS ( IDictionary<,> and IList<,>) ***/
/*
* DICTIONARY:
* Sample expression:
* "myObj => myObj.Property1.ListProperty[5].AdditionalInfo["KEY"]"
* Setter behaviour:
* The same as adding to a dictionary.
* It does Add("KEY", <value to be set>) to the dictionary. It fails if the jey already exists.
*
*
* LIST
* Sample expression:
* "myObj => myObj.Property1.ListProperty[INDEX]"
* Setter behaviour:
* If INDEX >= 0 and the index exists in the collection it behaves the same like inserting to a collection.
* IF INDEX < 0 (is negative) it adds the value at the end of the collection.
*/
var methodCallExpression = getterExpression.Body as MethodCallExpression;
if (
methodCallExpression != null && methodCallExpression.Object != null &&
methodCallExpression.Object.Type.IsGenericType)
{
var collectionGetterExpression = methodCallExpression.Object as MemberExpression;
Debug.Assert(collectionGetterExpression != null, "collectionGetterExpression != null");
// This gives us the collection instance when it is invoked on the object instance whic the expression is for
var collectionGetterCompiledLambda =Expression.Lambda(collectionGetterExpression, getterExpression.Parameters.Single()).Compile();
// this returns the "KEY" which is the key (object) in case of Dictionaries and Index (integer) in case of other collections
var collectionKey = ((ConstantExpression) methodCallExpression.Arguments[0]).Value;
var collectionType = collectionGetterExpression.Type;
// IDICTIONARY
if (collectionType.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
// Create an action which accepts the instance of the object which the "dictionary getter" expression is for and a value
// to be added to the dictionary.
Action<TObject, TPropertyOnObject> dictionaryAdder = (expressionParameter, value) =>
{
try
{
var dictionaryInstance = (IDictionary)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter);
dictionaryInstance.Add(collectionKey, value);
}
catch (Exception exception)
{
throw new Exception(
string.Format(
"Addition to dictionary failed [Key='{0}', Value='{1}']. The '"adder'" was generated from getter expression: '{2}'.",
collectionKey,
value,
getterExpression.ToString()), exception);
}
};
return dictionaryAdder;
}
// ILIST
if (typeof (IList<>).MakeGenericType(typeof (bool)).IsAssignableFrom(collectionType.GetGenericTypeDefinition().MakeGenericType(typeof(bool))))
{
// Create an action which accepts the instance of the object which the "collection getter" expression is for and a value
// to be inserted
Action<TObject, TPropertyOnObject> collectionInserter = (expressionParameter, value) =>
{
try
{
var collectionInstance = (IList<TPropertyOnObject>)collectionGetterCompiledLambda.DynamicInvoke(expressionParameter);
var collectionIndexFromExpression = int.Parse(collectionKey.ToString());
// The semantics of a collection setter is to add value if the index in expression is <0 and set the item at the index
// if the index >=0.
if (collectionIndexFromExpression < 0)
{
collectionInstance.Add(value);
}
else
{
collectionInstance[collectionIndexFromExpression] = value;
}
}
catch (Exception invocationException)
{
throw new Exception(
string.Format(
"Insertion to collection failed [Index='{0}', Value='{1}']. The '"inserter'" was generated from getter expression: '{2}'.",
collectionKey,
value,
getterExpression.ToString()), invocationException);
}
};
return collectionInserter;
}
throw new NotSupportedException(
string.Format(
"Cannot generate setter from the given expression: '{0}'. Collection type: '{1}' not supported.",
getterExpression, collectionType));
}
throw new NotSupportedException("Cannot generate setter from the given expression: "+getterExpression);
}
这是我的方式
public static Action<T, object> GenerateSetterAction<T>(PropertyInfo pi)
{
//p=> p.<pi>=(pi.PropertyType)v
var expParamP = Expression.Parameter(typeof(T), "p");
var expParamV = Expression.Parameter(typeof(object), "v");
var expParamVc = Expression.Convert(expParamV, pi.PropertyType);
var mma = Expression.Call(
expParamP
, pi.GetSetMethod()
, expParamVc
);
var exp = Expression.Lambda<Action<T, object>>(mma, expParamP, expParamV);
return exp.Compile();
}