Expression> to Expression> “Getter” to “Setter”

前端 未结 5 695
难免孤独
难免孤独 2020-12-28 10:15

I\'m new to expressions, and i\'d like to know how if it\'s in any way possible to convert my expression

Let\'s say in this example my TModel is of type Customer, an

5条回答
  •  庸人自扰
    2020-12-28 10:48

    Modified version. This class is probably better than many other ones you can find around :-) This is because this version support direct properties (p => p.B) (as everyone else :-) ), nested properties (p => p.B.C.D), fields (both "terminal" and "in the middle", so in p => p.B.C.D both B and D could be fields) and "inner" casting of types (so p => ((BType)p.B).C.D and p => (p.B as BType).C.D). The only thing that isn't supported is casting of the "terminal" element (so no p => (object)p.B).

    There are two "codepaths" in the generator: for simple Expressions (p => p.B) and for "nested" expressions. There are code variants for .NET 4.0 (that has the Expression.Assign expression type). From some benchmarks of mine the fastest delegates are: "simple" Delegate.CreateDelegate for properties, Expression.Assign for fields and "simple" FieldSetter for fields (this one is just a little slower than Expression.Assign for fields). So under .NET 4.0 you should take away all the code marked as 3.5.

    Part of the code isn't mine. The initial (simple) version was based on the Fluent NHibernate code (but it supported only direct properties), some other parts are based on code from How do I set a field value in an C# Expression tree? and Assignment in .NET 3.5 expression trees.

    public static class FluentTools
    {
        public static Action GetterToSetter(Expression> 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)Delegate.CreateDelegate(typeof(Action), 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(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>(expr, parameter, value).Compile();
        }
    
        private static void GetMemberExpression(Expression> 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 FieldSetter(FieldInfo field)
        {
            DynamicMethod m = new DynamicMethod("setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(FluentTools));
            ILGenerator cg = m.GetILGenerator();
    
            // arg0. = arg1
            cg.Emit(OpCodes.Ldarg_0);
            cg.Emit(OpCodes.Ldarg_1);
            cg.Emit(OpCodes.Stfld, field);
            cg.Emit(OpCodes.Ret);
    
            return (Action)m.CreateDelegate(typeof(Action));
        }
    
        // 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(ref T left, T right)  // note the 'ref', which is
        {                                                     // important when assigning
            left = right;                                     // to value types!
        }
    
        #endregion
    }
    

提交回复
热议问题