【问题标题】:How do I set a field value in an C# Expression tree?如何在 C# 表达式树中设置字段值?
【发布时间】:2010-09-24 05:07:03
【问题描述】:

给定:

FieldInfo field = <some valid string field on type T>;
ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");

如何编译 lambda 表达式以将“target”参数上的字段设置为“value”?

【问题讨论】:

    标签: c# lambda expression-trees


    【解决方案1】:

    .Net 4.0:现在有了Expression.Assign,这很容易做到:

    FieldInfo field = typeof(T).GetField("fieldName");
    ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
    ParameterExpression valueExp = Expression.Parameter(typeof(string), "value");
    
    // Expression.Property can be used here as well
    MemberExpression fieldExp = Expression.Field(targetExp, field);
    BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp);
    
    var setter = Expression.Lambda<Action<T, string>>
        (assignExp, targetExp, valueExp).Compile();
    
    setter(subject, "new value");
    

    .Net 3.5:你不能,你必须改用 System.Reflection.Emit:

    class Program
    {
        class MyObject
        {
            public int MyField;
        }
    
        static Action<T,TValue> MakeSetter<T,TValue>(FieldInfo field)
        {
            DynamicMethod m = new DynamicMethod(
                "setter", typeof(void), new Type[] { typeof(T), typeof(TValue) }, typeof(Program));
            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>));
        }
    
        static void Main()
        {
            FieldInfo f = typeof(MyObject).GetField("MyField");
    
            Action<MyObject,int> setter = MakeSetter<MyObject,int>(f);
    
            var obj = new MyObject();
            obj.MyField = 10;
    
            setter(obj, 42);
    
            Console.WriteLine(obj.MyField);
            Console.ReadLine();
        }
    }
    

    【讨论】:

    • 很好的回应巴里,你回答了我最初的问题。我将发布另一个问题,我需要先调用转换的操作码......谢谢!
    • 只是好奇,这种方法与仅使用 System.Reflection 和 MemberInfos 设置属性有什么区别?
    • 我对您的回答 +1 感到非常惊讶。干得好!恭喜:)
    • 非常有用的东西,但要小心极端情况!此 MakeSetter 方法不处理值类型的情况。
    • @Oleg Mihailik:你说不处理值类型是什么意思?如果该字段是值类型,则没有问题。如果源 (T) 是值类型,我认为没有任何 setter 可以真正按预期工作。
    【解决方案2】:

    如前所述,设置字段是有问题的。您可以(在 3.5 中)使用单个方法,例如属性设置器 - 但只能是间接的。正如 here 所讨论的,这在 4.0 中变得更加容易。但是,如果您确实有属性(而不是字段),您可以简单地使用 Delegate.CreateDelegate 做很多事情:

    using System;
    using System.Reflection;
    public class Foo
    {
        public int Bar { get; set; }
    }
    static class Program
    {
        static void Main()
        {
            MethodInfo method = typeof(Foo).GetProperty("Bar").GetSetMethod();
            Action<Foo, int> setter = (Action<Foo, int>)
                Delegate.CreateDelegate(typeof(Action<Foo, int>), method);
    
            Foo foo = new Foo();
            setter(foo, 12);
            Console.WriteLine(foo.Bar);
        }
    }
    

    【讨论】:

    • 我很想听听为什么它被否决了……对我来说似乎是一个相当不错的旁观者;仅适用于属性,但不需要 Reflection.Emit 或 Expression...
    • 在给定的链接中,提到了Expression.AssignProperty。那是什么?
    • @nawful 在 4.0 树中更加丰富,允许完整的代码块。然而,大多数 linq 提供程序和编译器都不允许您这样做。我假设它是通过属性分配的;)
    • @MarcGravell 你能否正确地标记我的名字,以便我收到警报。哦,好的,但是你从哪里找到这个 Expression.AssignProperty 的?一些文档或链接?
    • @nawfal 抱歉打错了;如果您阅读该帖子,它会说它基于 4.0 CTP - 即 .NET 4.0 的预发布版本;大概这在 RTM 版本中更改为 .Assign。
    【解决方案3】:
    private static Action<object, object> CreateSetAccessor(FieldInfo field)
        {
            DynamicMethod setMethod = new DynamicMethod(field.Name, typeof(void), new[] { typeof(object), typeof(object) });
            ILGenerator generator = setMethod.GetILGenerator();
            LocalBuilder local = generator.DeclareLocal(field.DeclaringType);
            generator.Emit(OpCodes.Ldarg_0);
            if (field.DeclaringType.IsValueType)
            {
                generator.Emit(OpCodes.Unbox_Any, field.DeclaringType);
                generator.Emit(OpCodes.Stloc_0, local);
                generator.Emit(OpCodes.Ldloca_S, local);
            }
            else
            {
                generator.Emit(OpCodes.Castclass, field.DeclaringType);
                generator.Emit(OpCodes.Stloc_0, local);
                generator.Emit(OpCodes.Ldloc_0, local);
            }
            generator.Emit(OpCodes.Ldarg_1);
            if (field.FieldType.IsValueType)
            {
                generator.Emit(OpCodes.Unbox_Any, field.FieldType);
            }
            else
            {
                generator.Emit(OpCodes.Castclass, field.FieldType);
            }
            generator.Emit(OpCodes.Stfld, field);
            generator.Emit(OpCodes.Ret);
            return (Action<object, object>)setMethod.CreateDelegate(typeof(Action<object, object>));
        }
    

    【讨论】:

    • 看来FieldInfo.DeclaringType 必须是公开的才能使其正常工作。否则,它会在 .NET 3.5 中出现 TypeLoadException 和在 .NET 4.0/4.5 中出现 TypeAccessException 失败(尽管 Expression.Assign 在 4.0+ 中可用,所以对于 3.5 来说问题更大)。
    • 啊,没关系。只需在DynamicMethod 构造函数中设置restrictedSkipVisibility
    【解决方案4】:

    实际上,在 .NET 3.5 中有一种方法可以使用表达式树设置属性和字段。它可能是一些不支持Delegate.CreateDelegate 的 PCL 配置文件的唯一选择(除了 Reflection.Emit):

    • 对于字段,诀窍是将字段作为 ref 参数传递,
      例如SetField(ref holder.Field, "NewValue");

    • 属性(正如 Marc 已经指出的那样)可以通过反射和调用它的 setter 方法来设置。

    下面提供了完整的概念证明作为 NUnit 测试夹具。

    [TestFixture]
    public class CanSetPropAndFieldWithExpressionTreeInNet35
    {
        class Holder
        {
            public int Field;
            public string Prop { get; set; }
        }
    
        public static class FieldAndPropSetter
        {
            public static T SetField<T, TField>(T holder, ref TField field, TField value)
            {
                field = value;
                return holder;
            }
    
            public static T SetProp<T>(T holder, Action<T> setProp)
            {
                setProp(holder);
                return holder;
            }
        }
    
        [Test]
        public void Can_set_field_with_expression_tree_in_Net35()
        {
            // Shows how expression could look like:
            Func<Holder, Holder> setHolderField = h => FieldAndPropSetter.SetField(h, ref h.Field, 111);
            var holder = new Holder();
            holder = setHolderField(holder);
            Assert.AreEqual(111, holder.Field);
    
            var holderType = typeof(Holder);
            var field = holderType.GetField("Field");
            var fieldSetterMethod =
                typeof(FieldAndPropSetter).GetMethod("SetField")
                .MakeGenericMethod(holderType, field.FieldType);
    
            var holderParamExpr = Expression.Parameter(holderType, "h");
            var fieldAccessExpr = Expression.Field(holderParamExpr, field);
    
            // Result expression looks like: h => FieldAndPropSetter.SetField(h, ref h.Field, 222)
            var setHolderFieldExpr = Expression.Lambda<Func<Holder, Holder>>(
                Expression.Call(fieldSetterMethod, holderParamExpr, fieldAccessExpr, Expression.Constant(222)),
                holderParamExpr);
    
            var setHolderFieldGenerated = setHolderFieldExpr.Compile();
            holder = setHolderFieldGenerated(holder);
            Assert.AreEqual(222, holder.Field);
        }
    
        [Test]
        public void Can_set_property_with_expression_tree_in_Net35()
        {
            // Shows how expression could look like:
            Func<Holder, Holder> setHolderProp = h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "ABC");
            var holder = new Holder();
            holder = setHolderProp(holder);
            Assert.AreEqual("ABC", holder.Prop);
    
            var holderType = typeof(Holder);
            var prop = holderType.GetProperty("Prop");
            var propSet = prop.GetSetMethod();
    
            var holderParamExpr = Expression.Parameter(holderType, "h");
            var callSetPropExpr = Expression.Call(holderParamExpr, propSet, Expression.Constant("XXX"));
            var setPropActionExpr = Expression.Lambda(callSetPropExpr, holderParamExpr);
    
            var propSetterMethod = typeof(FieldAndPropSetter).GetMethod("SetProp").MakeGenericMethod(holderType);
    
            // Result expression looks like: h => FieldAndPropSetter.SetProp(h, _ => _.Prop = "XXX")
            var setHolderPropExpr = Expression.Lambda<Func<Holder, Holder>>(
                Expression.Call(propSetterMethod, holderParamExpr, setPropActionExpr),
                holderParamExpr);
    
            var setHolderPropGenerated = setHolderPropExpr.Compile();
            holder = setHolderPropGenerated(holder);
            Assert.AreEqual("XXX", holder.Prop);
        }
    }
    

    【讨论】:

      【解决方案5】:

      我曾经做过这门课。也许有帮助:

      public class GetterSetter<EntityType,propType>
      {
          private readonly Func<EntityType, propType> getter;
          private readonly Action<EntityType, propType> setter;
          private readonly string propertyName;
          private readonly Expression<Func<EntityType, propType>> propertyNameExpression;
      
          public EntityType Entity { get; set; }
      
          public GetterSetter(EntityType entity, Expression<Func<EntityType, propType>> property_NameExpression)
          {
              Entity = entity;
              propertyName = GetPropertyName(property_NameExpression);
              propertyNameExpression = property_NameExpression;
              //Create Getter
              getter = propertyNameExpression.Compile();
              // Create Setter()
              MethodInfo method = typeof (EntityType).GetProperty(propertyName).GetSetMethod();
              setter = (Action<EntityType, propType>)
                       Delegate.CreateDelegate(typeof(Action<EntityType, propType>), method);
          }
      
      
          public propType Value
          {
              get
              {
                  return getter(Entity);
              }
              set
              {
                  setter(Entity, value);
              }
          }
      
          protected string GetPropertyName(LambdaExpression _propertyNameExpression)
          {
              var lambda = _propertyNameExpression as LambdaExpression;
              MemberExpression memberExpression;
              if (lambda.Body is UnaryExpression)
              {
                  var unaryExpression = lambda.Body as UnaryExpression;
                  memberExpression = unaryExpression.Operand as MemberExpression;
              }
              else
              {
                  memberExpression = lambda.Body as MemberExpression;
              }
              var propertyInfo = memberExpression.Member as PropertyInfo;
              return propertyInfo.Name;
          }
      

      测试:

      var gs = new GetterSetter<OnOffElement,bool>(new OnOffElement(), item => item.IsOn);
              gs.Value = true;
              var result = gs.Value;
      

      【讨论】:

      • 不回答问题。关于FieldInfo
      【解决方案6】:

      为了完整起见,这里是 getter:

          public static IEnumerable<Func<T, object>> GetTypeGetters<T>()
          {
              var fields = typeof (T).GetFields();
      
              foreach (var field in fields)
              {
                  ParameterExpression targetExp = Expression.Parameter(typeof(T), "target");
                  UnaryExpression boxedFieldExp = Expression.Convert(Expression.Field(targetExp, field), typeof(object));
                  yield return  Expression.Lambda<Func<T,object>>(boxedFieldExp, targetExp).Compile();
              }
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-04
        • 1970-01-01
        • 1970-01-01
        • 2017-05-07
        相关资源
        最近更新 更多