问题描述
我已经搜索了高低,因此为我的问题找到解决方案.
我找到了一些简单表达式的答案,例如
var exp1 Expression<Func<T, bool>> x => x.Name == "MyName"
,但是当表达式像:
时,我遇到了麻烦var exp1 Expression<Func<T, bool>> x => x.Category.Name == "Coupe"
对于简单的
,我能够将任何一种表达式从一种类型(t)转换为另一种(tt),在其他情况下,我也需要这样做,更复杂...
有人可以帮助一些指针吗?这是我到目前为止所拥有的:
private class CustomVisitor<T> : ExpressionVisitor { private readonly ParameterExpression mParameter; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } //this method replaces original parameter with given in constructor protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } private int counter = 0; /// <summary> /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />. /// </summary> /// <param name="node">The expression to visit.</param> /// <returns> /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. /// </returns> /// <exception cref="System.NotImplementedException"></exception> protected override Expression VisitMember(MemberExpression node) { counter++; System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter); try { //only properties are allowed if you use fields then you need to extend // this method to handle them if (node.Member.MemberType != System.Reflection.MemberTypes.Property) throw new NotImplementedException(); //name of a member referenced in original expression in your //sample Id in mine Prop var memberName = node.Member.Name; //find property on type T (=PersonData) by name var otherMember = typeof(T).GetProperty(memberName); //visit left side of this expression p.Id this would be p var inner = Visit(node.Expression); return Expression.Property(inner, otherMember); } catch (Exception ex) { return null; } } }
实用程序方法:
public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source) { var param = Expression.Parameter(typeof(TDestin)); var body = new CustomVisitor<TDestin>(param).Visit(source.Body); Expression<Func<TDestin, T>> lambda = Expression.Lambda<Func<TDestin, T>>(body, param); return lambda; }
,它被这样使用:
var changedFilter = ConvertTypesInExpression<ClientNotificationRuleDto, ClientNotificationRule, bool>(filterExpression);
因此,如果有人可以帮助一些伊迪亚斯或指针,那将是很棒的!
推荐答案
分析此测试:
class Replaced { public Inner Inner { get; set; } } class Inner { public string Name { get; set; } } class Replacing { public Inner Inner { get; set; } } [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { var parameter = Expression.Parameter(typeof(Replacing)); var visitor = new CustomVisitor(parameter); Expression<Func<Replaced, bool>> expression = x => x.Inner.Name == "ss"; var resultExpression = (Expression<Func<Replacing, bool>>)visitor.Visit(expression); var function = resultExpression.Compile(); var result = function(new Replacing { Inner = new Inner { Name = "ss" } }); Assert.IsTrue(result); } } internal class CustomVisitor : ExpressionVisitor { private readonly ParameterExpression mParameter; private int counter = 0; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda( Visit(node.Body), node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray()); //or simpler but less generic //return Expression.Lambda(Visit(node.Body), mParameter); } //this method will be called twice first for Name and then for Inner protected override Expression VisitMember(MemberExpression node) { counter++; System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter); if (node.Member.MemberType != System.Reflection.MemberTypes.Property) throw new NotImplementedException(); var memberName = node.Member.Name; var inner = Visit(node.Expression); var otherMember = inner.Type.GetProperty(memberName); return Expression.Property(inner, otherMember); } protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } }
请注意,访问成员被称为两次,必须对两个电话做出相应的反应.另外,您需要覆盖lambda的创建,因为它将在参数更换中失败.
ps:永远不要赶上基类例外,它只是不良习惯,而异常的恐慌回报是错误的.
其他推荐答案
在@rafal的宝贵帮助下,并从 https://stackoverflow commights提供了见解.需要
public static class EXpressionTreeTools { #region ConvertTypesInExpression /// <summary> /// Converts the types in the expression. /// </summary> /// <typeparam name="TSource">The source type (the "replacee").</typeparam> /// <typeparam name="TDestin">The destiny type (the replacer).</typeparam> /// <typeparam name="T">The type of the result fo the expression evaluation</typeparam> /// <param name="source">The source expression.</param> /// <returns></returns> public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source) { var parameter = Expression.Parameter(typeof(TDestin)); var visitor = new CustomVisitor(parameter); //Expression<Func<TSource, bool>> expression = x => x.Inner.Name == "ss"; Expression<Func<TDestin, T>> resultExpression = (Expression<Func<TDestin, T>>)visitor.Visit(source); return resultExpression; } #endregion #region CustomVisitor /// <summary> /// A custom "visitor" class to traverse expression trees /// </summary> /// <typeparam name="T"></typeparam> internal class CustomVisitor : ExpressionVisitor { private readonly ParameterExpression mParameter; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda( Visit(node.Body), node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray()); //or simpler but less generic //return Expression.Lambda(Visit(node.Body), mParameter); } //this method will be called twice first for Name and then for Inner protected override Expression VisitMember(MemberExpression node) { if (node.Member.MemberType != System.Reflection.MemberTypes.Property) //throw new NotImplementedException(); { Expression exp = this.Visit(node.Expression); if (exp == null || exp is ConstantExpression) // null=static member { object @object = exp == null ? null : ((ConstantExpression)exp).Value; object value = null; Type type = null; if (node.Member is FieldInfo) { FieldInfo fi = (FieldInfo)node.Member; value = fi.GetValue(@object); type = fi.FieldType; } else if (node.Member is PropertyInfo) { PropertyInfo pi = (PropertyInfo)node.Member; if (pi.GetIndexParameters().Length != 0) throw new ArgumentException("cannot eliminate closure references to indexed properties"); value = pi.GetValue(@object, null); type = pi.PropertyType; } return Expression.Constant(value, type); } else // otherwise just pass it through { return Expression.MakeMemberAccess(exp, node.Member); } } var memberName = node.Member.Name; var inner = Visit(node.Expression); var otherMember = inner.Type.GetProperty(memberName); return Expression.Property(inner, otherMember); } protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } } #endregion }
问题描述
I've searched high an low of SO to find a solution for my problem.
I've found several answers for when it comes to simple expressions like
var exp1 Expression<Func<T, bool>> x => x.Name == "MyName"
But I'm having trouble when the expressions are like:
var exp1 Expression<Func<T, bool>> x => x.Category.Name == "Coupe"
For the simple ones, I am able to convert any expression from one type (T) to another (TT), I need to do it also in the other cases, more complex...
Anyone who can help with some pointers? Here is what I've got so far:
private class CustomVisitor<T> : ExpressionVisitor { private readonly ParameterExpression mParameter; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } //this method replaces original parameter with given in constructor protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } private int counter = 0; /// <summary> /// Visits the children of the <see cref="T:System.Linq.Expressions.MemberExpression" />. /// </summary> /// <param name="node">The expression to visit.</param> /// <returns> /// The modified expression, if it or any subexpression was modified; otherwise, returns the original expression. /// </returns> /// <exception cref="System.NotImplementedException"></exception> protected override Expression VisitMember(MemberExpression node) { counter++; System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter); try { //only properties are allowed if you use fields then you need to extend // this method to handle them if (node.Member.MemberType != System.Reflection.MemberTypes.Property) throw new NotImplementedException(); //name of a member referenced in original expression in your //sample Id in mine Prop var memberName = node.Member.Name; //find property on type T (=PersonData) by name var otherMember = typeof(T).GetProperty(memberName); //visit left side of this expression p.Id this would be p var inner = Visit(node.Expression); return Expression.Property(inner, otherMember); } catch (Exception ex) { return null; } } }
Utility method:
public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source) { var param = Expression.Parameter(typeof(TDestin)); var body = new CustomVisitor<TDestin>(param).Visit(source.Body); Expression<Func<TDestin, T>> lambda = Expression.Lambda<Func<TDestin, T>>(body, param); return lambda; }
And it's being used like this:
var changedFilter = ConvertTypesInExpression<ClientNotificationRuleDto, ClientNotificationRule, bool>(filterExpression);
So if anyone can help with some ideias or pointers, that would be great!
推荐答案
Analyze this test:
class Replaced { public Inner Inner { get; set; } } class Inner { public string Name { get; set; } } class Replacing { public Inner Inner { get; set; } } [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { var parameter = Expression.Parameter(typeof(Replacing)); var visitor = new CustomVisitor(parameter); Expression<Func<Replaced, bool>> expression = x => x.Inner.Name == "ss"; var resultExpression = (Expression<Func<Replacing, bool>>)visitor.Visit(expression); var function = resultExpression.Compile(); var result = function(new Replacing { Inner = new Inner { Name = "ss" } }); Assert.IsTrue(result); } } internal class CustomVisitor : ExpressionVisitor { private readonly ParameterExpression mParameter; private int counter = 0; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda( Visit(node.Body), node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray()); //or simpler but less generic //return Expression.Lambda(Visit(node.Body), mParameter); } //this method will be called twice first for Name and then for Inner protected override Expression VisitMember(MemberExpression node) { counter++; System.Diagnostics.Debug.WriteLine("{0} - {1}", node.ToString(), counter); if (node.Member.MemberType != System.Reflection.MemberTypes.Property) throw new NotImplementedException(); var memberName = node.Member.Name; var inner = Visit(node.Expression); var otherMember = inner.Type.GetProperty(memberName); return Expression.Property(inner, otherMember); } protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } }
Note that visit member is called twice and must react accordingly for both calls. Also you need to override the lambda creation as it would fail in parameter replacement.
PS: Never catch base class Exception its just bad practice and the panic return null on exception is just wrong.
其他推荐答案
With the precious help from @Rafal and insights from this, I managed to achieve a solution for my needs
public static class EXpressionTreeTools { #region ConvertTypesInExpression /// <summary> /// Converts the types in the expression. /// </summary> /// <typeparam name="TSource">The source type (the "replacee").</typeparam> /// <typeparam name="TDestin">The destiny type (the replacer).</typeparam> /// <typeparam name="T">The type of the result fo the expression evaluation</typeparam> /// <param name="source">The source expression.</param> /// <returns></returns> public static Expression<Func<TDestin, T>> ConvertTypesInExpression<TSource, TDestin, T>(Expression<Func<TSource, T>> source) { var parameter = Expression.Parameter(typeof(TDestin)); var visitor = new CustomVisitor(parameter); //Expression<Func<TSource, bool>> expression = x => x.Inner.Name == "ss"; Expression<Func<TDestin, T>> resultExpression = (Expression<Func<TDestin, T>>)visitor.Visit(source); return resultExpression; } #endregion #region CustomVisitor /// <summary> /// A custom "visitor" class to traverse expression trees /// </summary> /// <typeparam name="T"></typeparam> internal class CustomVisitor : ExpressionVisitor { private readonly ParameterExpression mParameter; public CustomVisitor(ParameterExpression parameter) { mParameter = parameter; } protected override Expression VisitLambda<T>(Expression<T> node) { return Expression.Lambda( Visit(node.Body), node.Parameters.Select(x => (ParameterExpression)Visit(x)).ToArray()); //or simpler but less generic //return Expression.Lambda(Visit(node.Body), mParameter); } //this method will be called twice first for Name and then for Inner protected override Expression VisitMember(MemberExpression node) { if (node.Member.MemberType != System.Reflection.MemberTypes.Property) //throw new NotImplementedException(); { Expression exp = this.Visit(node.Expression); if (exp == null || exp is ConstantExpression) // null=static member { object @object = exp == null ? null : ((ConstantExpression)exp).Value; object value = null; Type type = null; if (node.Member is FieldInfo) { FieldInfo fi = (FieldInfo)node.Member; value = fi.GetValue(@object); type = fi.FieldType; } else if (node.Member is PropertyInfo) { PropertyInfo pi = (PropertyInfo)node.Member; if (pi.GetIndexParameters().Length != 0) throw new ArgumentException("cannot eliminate closure references to indexed properties"); value = pi.GetValue(@object, null); type = pi.PropertyType; } return Expression.Constant(value, type); } else // otherwise just pass it through { return Expression.MakeMemberAccess(exp, node.Member); } } var memberName = node.Member.Name; var inner = Visit(node.Expression); var otherMember = inner.Type.GetProperty(memberName); return Expression.Property(inner, otherMember); } protected override Expression VisitParameter(ParameterExpression node) { return mParameter; } } #endregion }