代码之家  ›  专栏  ›  技术社区  ›  Buu

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

  •  11
  • Buu  · 技术社区  · 15 年前

    假设我有以下代码,它更新了 struct 使用反射。因为结构实例被复制到 DynamicUpdate 方法, it needs to be boxed to an object before being passed .

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

    代码工作正常。现在,假设我不想使用反射,因为它很慢。相反,我想生成一些CIL直接修改 id 字段并将该CIL转换为可重用委托(例如,使用动态方法功能)。特别是,我想用这样的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<object, object> CreateSetIdDelegate(Type t)
    {
        // build dynamic method and return delegate
    }    
    

    我的问题: 有什么办法可以实施吗 CreateSetIdDelegate 除了使用以下技术之一?

    1. 生成使用反射调用setter的CIL(作为本文的第一个代码段)。这是没有意义的,因为要求消除反射,但这是一个可能的实现,所以我只是提一下。
    2. 而不是使用 Action<object, object> ,使用签名为的自定义委托 public delegate void Setter(ref object target, object value) .
    3. 而不是使用 操作对象,对象 使用 Action<object[], object> 数组的第一个元素是目标对象。

    我不喜欢2&3的原因是我不想为对象的setter和结构的setter使用不同的委托(也不想使set对象字段委托变得比必要的更复杂,例如。 操作对象,对象 )我估计实施 创建集ID委托 将根据目标类型是struct还是object生成不同的cil,但我希望它返回为用户提供相同API的相同委托。

    6 回复  |  直到 15 年前
        1
  •  14
  •   Sam Harwell    15 年前

    再次编辑 :这现在起作用。

    在C 4中有一个很好的方法,但是你必须自己写 ILGenerator 为之前的任何内容发出代码。他们增加了一个 ExpressionType.Assign 到.NET Framework 4。

    这在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<ByRefStructAction> expr =
            Expression.Lambda<ByRefStructAction>(
                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);
    }
    
        2
  •  10
  •   Bryce Wagner    15 年前

    我遇到了一个类似的问题,花了我一个周末的大部分时间,但经过大量的搜索、阅读和分解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));
    }
    

    注意里面的“如果不安全的话”。实际上我想出了两种方法,但第一种方法是…粗俗的。引用ECMA-335,IL标准文件:

    “与需要复制值类型以便在对象中使用的Box不同,Unbox不需要从对象中复制值类型。通常,它只计算装箱对象中已经存在的值类型的地址。”

    因此,如果你想玩危险的游戏,你可以使用opcodes.unbox将你的对象句柄更改为指向你的结构的指针,然后它可以被用作stfld或callvirt的第一个参数。这样做实际上最终会在适当的地方修改结构,甚至不需要通过引用传递目标对象。

    但是,请注意,标准并不能保证UNBOX会给您一个指向装箱版本的指针。特别是,它建议nullable<gt;可以导致unbox创建副本。无论如何,如果发生这种情况,您可能会得到一个静默的失败,它在本地副本上设置字段或属性值,然后立即丢弃。

    这样做的安全方法是通过引用传递对象,将地址存储在一个局部变量中,进行修改,然后重新绑定结果并将其放回by ref对象参数中。

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

    带1个字段的结构: .46 S“不安全”代表 .70 S“安全”代表 4.5 s fieldinfo.setvalue设置值

    包含4个字段的结构: .46 S“不安全”代表 .88 S“安全”代表 4.5 s fieldinfo.setvalue设置值

    请注意,装箱使“安全”版本速度随结构大小而降低,而其他两种方法不受结构大小的影响。我想在某种程度上拳击的费用会超过反射的费用。但我不相信“不安全”版本有任何重要的作用。

        3
  •  5
  •   artelk    11 年前

    经过一些实验:

    public delegate void ClassFieldSetter<in T, in TValue>(T target, TValue value) where T : class;
    
    public delegate void StructFieldSetter<T, in TValue>(ref T target, TValue value) where T : struct;
    
    public static class FieldSetterCreator
    {
        public static ClassFieldSetter<T, TValue> CreateClassFieldSetter<T, TValue>(FieldInfo field)
            where T : class
        {
            return CreateSetter<T, TValue, ClassFieldSetter<T, TValue>>(field);
        }
    
        public static StructFieldSetter<T, TValue> CreateStructFieldSetter<T, TValue>(FieldInfo field)
            where T : struct
        {
            return CreateSetter<T, TValue, StructFieldSetter<T, TValue>>(field);
        }
    
        private static TDelegate CreateSetter<T, TValue, TDelegate>(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);
        }
    }
    

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

        4
  •  3
  •   ghord    9 年前

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

    private Action<object, object> 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<object, object>)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));
    }
    
        5
  •  1
  •   AJ.NET    15 年前

    您可能想看看动态方法(反射不一定要慢!)…

    格哈德在这方面有一个很好的帖子: http://jachman.wordpress.com/2006/08/22/2000-faster-using-dynamic-method-calls/

        6
  •  0
  •   D.R. Payne    15 年前

    您可以很容易地修改这个来使用结构。它目前是基于词典的,但你的情况更容易。

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