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

C# 生成动态方法来设置结构的字段,而不是使用反射,c#,.net,reflection,reflection.emit,dynamic-method,C#,.net,Reflection,Reflection.emit,Dynamic Method,假设我有以下代码,它使用反射更新struct的字段。由于struct实例被复制到DynamicUpdate方法中 代码运行良好。现在,假设我不想使用反射,因为它很慢。相反,我想直接修改id字段生成一些CIL,并将该CIL转换为可重用的委托(例如,使用动态方法特性)。我特别想用这样的s/t替换上面的代码: static void Main() { var action = CreateSetIdDelegate(typeof(Person)); object person = Ru

假设我有以下代码,它使用反射更新
struct
的字段。由于struct实例被复制到
DynamicUpdate
方法中

代码运行良好。现在,假设我不想使用反射,因为它很慢。相反,我想直接修改
id
字段生成一些CIL,并将该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
}    
static void Main()
{
var action=CreateSetIdDelegate(typeof(Person));
objectperson=RuntimeHelpers.GetObjectValue(newperson());
行动(人,10);
Console.WriteLine(((Person)Person.id);//打印10
}
私有静态操作CreateSetIdDelegate(类型t)
{
//构建动态方法并返回委托
}    

我的问题:除了使用以下技术之外,还有什么方法可以实现
CreateSetIdDelegate

  • 生成使用反射调用setter的CIL(作为本文的第一个代码段)。这毫无意义,因为要求消除反射,但这是一种可能的实现,所以我只提到
  • 不要使用
    操作
    ,而是使用签名为
    公共委托无效设置器(ref object target,object value)的自定义委托
  • 不要使用
    Action
    ,而是使用
    Action
    ,数组的第一个元素是目标对象
    我不喜欢2和3的原因是,我不想让对象的setter和结构的setter拥有不同的委托(也不想让set对象字段委托变得过于复杂,例如,
    操作
    )。我认为,
    CreateSetIdDelegate
    的实现将根据目标类型是struct还是object生成不同的CIL,但我希望它返回给用户提供相同API的同一个委托。

    您可能想看看动态方法(反射不必慢!)


    格哈德有一篇关于这一点的好文章:

    再次编辑::现在可以使用结构了

    在C#4中有一种很好的方法可以做到这一点,但在此之前,您必须为任何东西编写自己的
    ILGenerator
    emit代码。他们在.NETFramework4中添加了一个
    ExpressionType.Assign

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


    我遇到了一个类似的问题,花了我一个周末的时间,但经过大量的搜索、阅读和分解C#test项目后,我终于找到了答案。这个版本只需要.NET2,而不是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不同,box需要复制对象中使用的值类型,而unbox不需要复制对象中的值类型。通常它只计算已装箱对象中已存在的值类型的地址

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

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

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

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

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

    结构包含4个字段: .46 s“不安全”代表 .88 s“安全”代表 4.5 s FieldInfo.SetValue


    请注意,装箱使“安全”版本速度随结构大小而降低,而其他两种方法不受结构大小的影响。我想在某个时候,装箱成本会超过反射成本。但是我不相信“不安全”版本在任何重要的功能上。

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


    经过一些实验后:

    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);
        }
    }
    
    public delegate void ClassFieldSetter(T target,TValue value),其中T:class;
    公共委托void StructFieldSetter(ref T target,TValue value),其中T:struct;
    公共静态类FieldSetterCreator
    {
    公共静态类字段设置器CreateClassFieldSetter(FieldInfo字段)
    T:在哪里上课
    {
    返回CreateSetter(字段);
    }
    公共静态StructFieldSetter CreateStructFieldSetter(FieldInfo字段)
    其中T:struct
    {
    返回CreateSetter(字段);
    }
    专用静态TDelegate CreateSetter(FieldInfo字段)
    {
    return(TDelegate)(object)CreateSetter(字段、typeof(T)、typeof(TValue)、typeof(TDelegate));
    }
    私有静态委托CreateSetter(FieldInfo字段,类型instanceType,类型valueType,类型delegateType)
    {
    如果(!field.DeclaringType.IsAssignableFrom(instanceType))
    抛出新ArgumentException(“该字段声明为不同类型”);
    如果(!field.FieldType.IsAssignableFrom(valueType))
    抛出新ArgumentException(“字段类型不能从值赋值”);
    var paramType=instan
    
    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);
    }
    
    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));
    }
    
    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);
        }
    }
    
    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));
    }