Выражение <Func <TModel, string >> to Expression <Action <TModel>> «Getter» - «Setter» - PullRequest
9 голосов
/ 11 октября 2011

Я новичок в выражениях, и я хотел бы знать, как, если возможно, каким-либо образом преобразовать мое выражение

Допустим, в этом примере мой 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, вконкретное значение.

Ответы [ 5 ]

11 голосов
/ 11 октября 2011

Модифицированная версия. Этот класс, вероятно, лучше, чем многие другие, которые вы можете найти вокруг :-) Это потому, что эта версия поддерживает прямые свойства (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
}
2 голосов
/ 11 октября 2011
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);
}
1 голос
/ 11 октября 2011

У меня есть этот вспомогательный метод, который возвращает информацию о свойстве:

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, чтобы установить значение свойства для класса.

Вам нужно будет изменить код в соответствии с вашими потребностями, но, надеюсь, это поможет.

0 голосов
/ 29 сентября 2014

Поскольку правильный ответ не работал для меня (наборы в выражении), но подтолкнул меня в правильном направлении, мне нужно было много раз исследовать это, и я думаю, что я придумал метод, который может генерировать сеттер для буквально любоговыражение члена.

Для свойств и полей он ведет себя так же, как отмеченный ответ (хотя я считаю, что он гораздо более прозрачен).

Имеется дополнительная поддержка списков и словарей - см. В комментариях.

public static Action<TObject, TPropertyOnObject> GetSetter<TObject, TPropertyOnObject>(Expression<Func<TObject, TPropertyOnObject>> getterExpression)
    {
        /*** SIMPLE PROPERTIES AND FIELDS ***/
        // check if the getter expression reffers 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 "dictionarry 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);
    }
0 голосов
/ 10 июля 2014

это мой путь

    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();
    }
...