生成动态方法以设置结构的字段而不是使用reflection

假设我有以下代码使用reflection更新struct的字段。 由于struct实例被复制到DynamicUpdate方法中, 因此需要在传递之前将其装箱到对象 。

 struct Person { public int id; } class Test { static void Main() { object person = RuntimeHelpers.GetObjectValue(new Person()); DynamicUpdate(person); Console.WriteLine(((Person)person).id); // print 10 } private static void DynamicUpdate(object o) { FieldInfo field = typeof(Person).GetField("id"); field.SetValue(o, 10); } } 

代码工作正常。 现在,假设我不想使用reflection,因为它很慢。 相反,我想生成一些直接修改id字段的CIL并将该CIL转换为可重用的委托(例如,使用动态方法function)。 特别是,我想用s / t替换上面的代码,如下所示:

 static void Main() { var action = CreateSetIdDelegate(typeof(Person)); object person = RuntimeHelpers.GetObjectValue(new Person()); action(person, 10); Console.WriteLine(((Person)person).id); // print 10 } private static Action CreateSetIdDelegate(Type t) { // build dynamic method and return delegate } 

我的问题:有没有办法实现CreateSetIdDelegate除了使用以下技术之一?

  1. 生成使用reflection调用setter的CIL(作为本文中的第一个代码段)。 这没有任何意义,因为要求是摆脱reflection,但这是一个可能的实现,所以我只是提到。
  2. 而不是使用Action ,使用自定义委托,其签名是public delegate void Setter(ref object target, object value)
  3. 不使用Action ,而是使用Action ,并将数组的第一个元素作为目标对象。

我不喜欢2和3的原因是因为我不想为对象的setter和struct的setter设置不同的委托(以及不希望使set-object-field委托更复杂而不是必要的,例如Action )。 我认为CreateSetIdDelegate的实现会根据目标类型是struct还是object来生成不同的CIL,但我希望它返回向用户提供相同API的同一委托。

再次编辑 :这个结果现在结构。

在C#4中有一种很棒的方法,但在此之前你必须编写自己的ILGenerator发射代码。 他们在.NET Framework 4中添加了ExpressionType.Assign

这适用于C#4(已测试):

 public delegate void ByRefStructAction(ref SomeType instance, object value); private static ByRefStructAction BuildSetter(FieldInfo field) { ParameterExpression instance = Expression.Parameter(typeof(SomeType).MakeByRefType(), "instance"); ParameterExpression value = Expression.Parameter(typeof(object), "value"); Expression expr = Expression.Lambda( Expression.Assign( Expression.Field(instance, field), Expression.Convert(value, field.FieldType)), instance, value); return expr.Compile(); } 

编辑:这是我的测试代码。

 public struct SomeType { public int member; } [TestMethod] public void TestIL() { FieldInfo field = typeof(SomeType).GetField("member"); var setter = BuildSetter(field); SomeType instance = new SomeType(); int value = 12; setter(ref instance, value); Assert.AreEqual(value, instance.member); } 

我遇到了类似的问题,它花了我一个周末的大部分时间,但经过大量搜索,阅读和反汇编C#测试项目后,我终于弄明白了。 而这个版本只需要.NET 2,而不是4。

 public delegate void SetterDelegate(ref object target, object value); private static Type[] ParamTypes = new Type[] { typeof(object).MakeByRefType(), typeof(object) }; private static SetterDelegate CreateSetMethod(MemberInfo memberInfo) { Type ParamType; if (memberInfo is PropertyInfo) ParamType = ((PropertyInfo)memberInfo).PropertyType; else if (memberInfo is FieldInfo) ParamType = ((FieldInfo)memberInfo).FieldType; else throw new Exception("Can only create set methods for properties and fields."); DynamicMethod setter = new DynamicMethod( "", typeof(void), ParamTypes, memberInfo.ReflectedType.Module, true); ILGenerator generator = setter.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldind_Ref); if (memberInfo.DeclaringType.IsValueType) { #if UNSAFE_IL generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType); #else generator.DeclareLocal(memberInfo.DeclaringType.MakeByRefType()); generator.Emit(OpCodes.Unbox, memberInfo.DeclaringType); generator.Emit(OpCodes.Stloc_0); generator.Emit(OpCodes.Ldloc_0); #endif // UNSAFE_IL } generator.Emit(OpCodes.Ldarg_1); if (ParamType.IsValueType) generator.Emit(OpCodes.Unbox_Any, ParamType); if (memberInfo is PropertyInfo) generator.Emit(OpCodes.Callvirt, ((PropertyInfo)memberInfo).GetSetMethod()); else if (memberInfo is FieldInfo) generator.Emit(OpCodes.Stfld, (FieldInfo)memberInfo); if (memberInfo.DeclaringType.IsValueType) { #if !UNSAFE_IL generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldloc_0); generator.Emit(OpCodes.Ldobj, memberInfo.DeclaringType); generator.Emit(OpCodes.Box, memberInfo.DeclaringType); generator.Emit(OpCodes.Stind_Ref); #endif // UNSAFE_IL } generator.Emit(OpCodes.Ret); return (SetterDelegate)setter.CreateDelegate(typeof(SetterDelegate)); } 

注意那里的“#if UNSAFE_IL”内容。 我实际上提出了两种方法来做到这一点,但第一种方法真的是… hackish。 引用Ecma-335,IL的标准文件:

“与用于在对象中使用的值类型的副本所需的box不同,unbox不需要从对象复制值类型。通常,它只是计算已经存在的值类型的地址。盒装物品。“

因此,如果您想要危险地玩,可以使用OpCodes.Unbox将对象句柄更改为指向结构的指针,然后可以将其用作Stfld或Callvirt的第一个参数。 这样做实际上最终会修改结构,你甚至不需要通过ref传递目标对象。

但请注意,该标准并不保证Unbox会为您提供指向盒装版本的指针。 特别是,它表明Nullable <>可以使Unbox创建一个副本。 无论如何,如果发生这种情况,您可能会遇到静默失败,它会在本地副本上设置字段或属性值,然后立即将其丢弃。

因此,安全的方法是通过ref传递对象,将地址存储在局部变量中,进行修改,然后重新生成结果并将其放回ByRef对象参数中。

我做了一些粗略的时间,每个版本调用10,000,000次,有两种不同的结构:

具有1个字段的结构:.46 s“不安全”委托.70 s“安全”委托4.5 s FieldInfo.SetValue

具有4个字段的结构:.46 s“不安全”委托.88 s“安全”委托4.5 s FieldInfo.SetValue

请注意,装箱使“安全”版本速度随结构大小减小,而其他两种方法不受结构大小的影响。 我想在某些时候拳击成本会超过reflection成本。 但我不相信任何重要能力的“不安全”版本。

经过一些实验:

 public delegate void ClassFieldSetter(T target, TValue value) where T : class; public delegate void StructFieldSetter(ref T target, TValue value) where T : struct; public static class FieldSetterCreator { public static ClassFieldSetter CreateClassFieldSetter(FieldInfo field) where T : class { return CreateSetter>(field); } public static StructFieldSetter CreateStructFieldSetter(FieldInfo field) where T : struct { return CreateSetter>(field); } private static TDelegate CreateSetter(FieldInfo field) { return (TDelegate)(object)CreateSetter(field, typeof(T), typeof(TValue), typeof(TDelegate)); } private static Delegate CreateSetter(FieldInfo field, Type instanceType, Type valueType, Type delegateType) { if (!field.DeclaringType.IsAssignableFrom(instanceType)) throw new ArgumentException("The field is declared it different type"); if (!field.FieldType.IsAssignableFrom(valueType)) throw new ArgumentException("The field type is not assignable from the value"); var paramType = instanceType.IsValueType ? instanceType.MakeByRefType() : instanceType; var setter = new DynamicMethod("", typeof(void), new[] { paramType, valueType }, field.DeclaringType.Module, true); var generator = setter.GetILGenerator(); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.Emit(OpCodes.Stfld, field); generator.Emit(OpCodes.Ret); return setter.CreateDelegate(delegateType); } } 

与表达式树方法的主要区别在于,只读字段也可以更改。

此代码适用于不使用ref的结构:

 private Action CreateSetter(FieldInfo field) { var instance = Expression.Parameter(typeof(object)); var value = Expression.Parameter(typeof(object)); var body = Expression.Block(typeof(void), Expression.Assign( Expression.Field( Expression.Unbox(instance, field.DeclaringType), field), Expression.Convert(value, field.FieldType))); return (Action)Expression.Lambda(body, instance, value).Compile(); } 

这是我的测试代码:

 public struct MockStruct { public int[] Values; } [TestMethod] public void MyTestMethod() { var field = typeof(MockStruct).GetField(nameof(MockStruct.Values)); var setter = CreateSetter(field); object mock = new MockStruct(); //note the boxing here. setter(mock, new[] { 1, 2, 3 }); var result = ((MockStruct)mock).Values; Assert.IsNotNull(result); Assert.IsTrue(new[] { 1, 2, 3 }.SequenceEqual(result)); } 

你可能想看一下动态方法(reflection不一定要慢!)……

Gerhard有一篇很好的post: http : //jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/

您可以相当容易地修改它以使用结构。 它目前基于字典,但您的情况更容易。

http://www.damonpayne.com/2009/09/07/TwoWayBindingToNameValuePairs.aspx