【问题标题】:Using FieldInfo.SetValue vs LINQ expressions to set a field in a struct使用 FieldInfo.SetValue 与 LINQ 表达式在结构中设置字段
【发布时间】:2015-08-22 16:35:21
【问题描述】:

我想使用 LINQ 表达式设置私有字段。我有这个代码:

//parameter "target", the object on which to set the field `field`
ParameterExpression targetExp = Expression.Parameter(typeof(object), "target");

//parameter "value" the value to be set in the `field` on "target"
ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");

//cast the target from object to its correct type
Expression castTartgetExp = Expression.Convert(targetExp, type);

//cast the value to its correct type
Expression castValueExp = Expression.Convert(valueExp, field.FieldType);

//the field `field` on "target"
MemberExpression fieldExp = Expression.Field(castTartgetExp, field);

//assign the "value" to the `field` 
BinaryExpression assignExp = Expression.Assign(fieldExp, castValueExp);

//compile the whole thing
var setter = Expression.Lambda<Action<object, object>> (assignExp, targetExp, valueExp).Compile();

这编译了一个接受两个对象的委托:目标和值:

setter(someObject, someValue);

type变量指定目标的Typefield变量是FieldInfo指定要设置的字段。

这对引用类型很有用,但是如果目标是一个结构,那么这个东西会将目标作为副本传递给 setter 委托并在副本上设置值,而不是像在原始目标上设置值我想。 (至少我认为是这样的。)

另一方面,

field.SetValue(someObject, someValue);

工作得很好,即使是结构。

为了使用编译的表达式设置目标的字段,我能做些什么吗?

【问题讨论】:

  • 因为你不能使用ref,唯一的办法就是分配返回值。

标签: c# .net reflection linq-expressions


【解决方案1】:

对于值类型,使用Expression.Unbox 而不是Expression.Convert

//cast the target from object to its correct type
Expression castTartgetExp = type.IsValueType
    ? Expression.Unbox(targetExp, type)
    : Expression.Convert(targetExp, type);

这是一个演示:.NET Fiddle


问:setter 方法没有ref 参数。它如何更新原始结构?

答:虽然确实没有ref关键字,值类型通常是按值传递并因此被复制,但这里target参数的类型是object。如果参数是一个装箱的结构,那么对盒子的 reference 会(按值)传递给方法。

现在,不可能使用纯 C# 来改变装箱结构,因为 C# 拆箱转换总是会生成装箱值的副本。但它可能使用 IL 或反射:

public struct S { public int I; }

public void M(object o, int i)
{
    // ((S)o).I = i; // DOESN'T COMPILE
    typeof(S).GetField("I").SetValue(o, i);
}

public void N()
{
    S s = new S();
    object o = s; // create a boxed copy of s

    M(o, 1); // mutate o (but not s)
    Console.WriteLine(((S)o).I); // "1"
    Console.WriteLine(s.I);      // "0"

    M(s, 2); // mutate a TEMPORARY boxed copy of s (BEWARE!)
    Console.WriteLine(s.I);      // "0"
}

问:如果 LINQ 表达式使用 Expression.Convert,为什么 setter 不起作用?

答: Expression.Convert 编译为unbox.any IL 指令,该指令返回target 引用的结构的副本。然后 setter 更新这个副本(随后被丢弃)。

问:为什么 Expression.Unbox 可以解决问题?

答: Expression.Unbox(当用作 Expression.Assign 的目标时)编译为unbox IL 指令,该指令返回一个指向所引用结构的 指针通过target。然后 setter 使用指针直接修改该结构。

【讨论】:

  • 这是问题所在。不是答案。
  • 您忽略了结构被传递给 lambda 的事实(即使是无类型的,也不会产生影响)。
  • 不过,我和@leppie 一样好奇为什么将值类型传递给委托时没有复制它(?)
  • @leppie:我已经用(希望)清晰的解释更新了我的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-29
  • 2021-12-28
  • 2015-03-01
  • 1970-01-01
相关资源
最近更新 更多