【问题标题】:Expression<Func<TModel,string>> to Expression<Action<TModel>> "Getter" to "Setter"表达式<Func<TModel,string>> 到表达式<Action<TModel>> “Getter” 到 “Setter”
【发布时间】:2011-10-11 09:10:54
【问题描述】:

我是表达式的新手,我想知道如何以任何方式转换我的表达式

假设在这个例子中我的 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 表达式指定的客户名称设置为特定值。

【问题讨论】:

  • 如果没有getter(即属性只有setter)会怎样?
  • 我不是在这里创建一个通用的解决方案。我计划使用它的属性将具有可访问的 getter 和 setter,我只是想知道如何构建 Setter
  • 看看这个问题的第一个答案。 stackoverflow.com/questions/2823236/…
  • 也许你应该选择另一个方向——从 setter 到 getter。
  • 基于 CreateDelegate 的方法通常比编译基于表达式树的方法更快。我还建议在stackoverflow.com/questions/2823236/… 或我这里stackoverflow.com/a/16078960/661933 中的 Marc 的回答中提到的那个。

标签: c# lambda expression


【解决方案1】:

修改版。这个类可能比你可以找到的许多其他类更好:-) 这是因为这个版本支持直接属性(p =&gt; p.B)(和其他人一样:-))、嵌套属性(p =&gt; p.B.C.D)、字段(两者都是“终端”和“中间”,所以在p =&gt; p.B.C.DBD 都可以是字段)和类型的“内部”转换(所以p =&gt; ((BType)p.B).C.Dp =&gt; (p.B as BType).C.D)。唯一不是支持的是“终端”元素的转换(所以没有p =&gt; (object)p.B)。

生成器中有两个“代码路径”:用于简单表达式 (p =&gt; p.B) 和用于“嵌套”表达式。 .NET 4.0 有代码变体(具有 Expression.Assign 表达式类型)。从我的一些基准测试中,最快的代表是:“简单”Delegate.CreateDelegate 用于属性,Expression.Assign 用于字段,“简单”FieldSetter 用于字段(这个比Expression.Assign 字段慢一点)。所以在.NET 4.0下你应该去掉所有标记为3.5的代码。

部分代码不是我的。初始(简单)版本基于 Fluent NHibernate 代码(但它仅支持直接属性),其他一些部分基于来自 How do I set a field value in an C# Expression tree?Assignment in .NET 3.5 expression trees 的代码。

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
}

【讨论】:

  • 这看起来很有希望,我在 CreateDelegate 调用中遇到了一个参数异常,“错误绑定到目标方法。”调试模式下的表达式有一个 NodeType 参数,我要设置的成员是 DateTime
  • @Mvision 你想绑定什么?这仅适用于公共属性。
  • 我假设我绑定的所有属性都是公开的,我从 EF4 实体中挑选它们,如下所示:customer=>customer.DateCreated 等
  • @Mvision 在这里它可以正常工作。我刚刚生成了一个 EF 模型来测试它。尝试查看 DateCreated 的定义(右键单击,转到定义)
  • public global::System.DateTime Datecreated,我使用“object”作为 U 很难,因为我不确定该值是什么类型,可能是这样吗?
【解决方案2】:
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);
}

【讨论】:

    【解决方案3】:

    我有这个帮助方法,它返回属性的属性信息:

    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) =&gt; c.PropertyName);

    然后您可以使用 PropertyInfo 设置类的属性值。

    您需要修改代码以满足您的需要,但希望它会有所帮助。

    【讨论】:

      【解决方案4】:

      由于正确的答案对我不起作用(表达式中的集合)但将我推向了正确的方向,我需要对此进行大量调查,我想我想出了一种可以为几乎任何成员表达。

      对于属性和字段,它的行为与标记的答案相同(但我相信它更加透明)。

      它对列表和字典有额外的支持 - 请在 cmets 中查看。

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

      【讨论】:

        【解决方案5】:

        这是我的方式

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-09-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多