如何用OpCodes与Emit将泛型T实例属性赋值给ref参数?
问题描述
需要实现一个泛型类方法,返回DynamicMethod,调用该方法生成的委托时,会将第一个ref参数设置为类实例化时设置的Value属性值。当前代码仅支持string类型,需要适配所有类型(引用类型、值类型、Task、接口及抽象类型)。
现有代码如下:
using HarmonyLib; using System.Reflection; using System.Reflection.Emit; public class OriginalCode<T> { public delegate void PostFix(ref T __result); public T Value { get; set; } public OriginalCode(T value) { Value = value; } public MethodInfo Postfix() { var result = new DynamicMethod("NewPostfix", typeof(void), new Type[] { typeof(T).MakeByRefType() }, typeof(Program).Module); result.DefineParameter(1, ParameterAttributes.None, "__result"); ILGenerator il = result.GetILGenerator(); Type type = typeof(OriginalCode<T>); PropertyInfo propertyInfo = type.GetProperty("Value"); // create a reference to the Value Property PropertyInfo ValueProperty = typeof(OriginalCode<T>).GetProperty("Value"); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldstr, Value); il.Emit(OpCodes.Stind_Ref); il.Emit(OpCodes.Ret); return result; } }
使用方式:
string testRefStr = "Test"; var originalCodeInstance = new OriginalCode<string>("New Test"); var newStringDelegate = (OriginalCode<string>.PostFix)originalCodeInstance.Postfix().CreateDelegate(typeof(OriginalCode<string>.PostFix)); newStringDelegate(ref testRefStr); int testRefInt = 55; var originalCodeInstance = new OriginalCode<int>(75); var newIntDelegate = (OriginalCode<int>.PostFix)originalCodeInstance.Postfix().CreateDelegate(typeof(OriginalCode<int>.PostFix)); newIntDelegate(ref testRefInt);
当前代码问题:仅支持string类型,其他类型运行时会报错,且无法正确访问实例的Value属性。
解决方案
核心问题分析
- 原代码使用
OpCodes.Ldstr硬编码加载字符串常量,仅适用于string类型,无法处理其他类型的Value。 DynamicMethod未关联当前OriginalCode<T>实例,无法获取实例的Value属性值。- 未区分值类型和引用类型的存储指令,导致值类型赋值失败。
修正后的代码
using System.Reflection; using System.Reflection.Emit; public class OriginalCode<T> { public delegate void PostFix(ref T __result); public T Value { get; set; } public OriginalCode(T value) { Value = value; } public MethodInfo Postfix() { // DynamicMethod参数列表:第一个是当前OriginalCode<T>实例,第二个是ref T参数 var methodParams = new Type[] { typeof(OriginalCode<T>), typeof(T).MakeByRefType() }; var result = new DynamicMethod( "NewPostfix", typeof(void), methodParams, typeof(OriginalCode<T>).Module, skipVisibility: true); // 定义参数名称,方便调试 result.DefineParameter(2, ParameterAttributes.None, "__result"); ILGenerator il = result.GetILGenerator(); PropertyInfo valueProperty = typeof(OriginalCode<T>).GetProperty(nameof(Value))!; MethodInfo valueGetter = valueProperty.GetGetMethod(nonPublic: false)!; // IL指令流程: // 1. 加载ref参数的地址(第二个参数,索引1) il.Emit(OpCodes.Ldarg_1); // 2. 加载当前OriginalCode<T>实例(第一个参数,索引0) il.Emit(OpCodes.Ldarg_0); // 3. 调用Value属性的getter方法,获取实例的Value值 il.Emit(OpCodes.Call, valueGetter); // 4. 根据T的类型选择存储指令:值类型用Stobj,引用类型用Stind_Ref if (typeof(T).IsValueType) { il.Emit(OpCodes.Stobj, typeof(T)); } else { il.Emit(OpCodes.Stind_Ref); } // 5. 返回 il.Emit(OpCodes.Ret); return result; } }
使用方式调整
创建委托时需要传入当前实例作为绑定目标,调用委托时会自动将实例传递给DynamicMethod:
// 字符串类型测试 string testRefStr = "Test"; var stringInstance = new OriginalCode<string>("New Test"); var stringDelegate = (OriginalCode<string>.PostFix)stringInstance.Postfix().CreateDelegate(typeof(OriginalCode<string>.PostFix), stringInstance); stringDelegate(ref testRefStr); // testRefStr 现在为 "New Test" // 值类型测试 int testRefInt = 55; var intInstance = new OriginalCode<int>(75); var intDelegate = (OriginalCode<int>.PostFix)intInstance.Postfix().CreateDelegate(typeof(OriginalCode<int>.PostFix), intInstance); intDelegate(ref testRefInt); // testRefInt 现在为75 // Task类型测试 Task testRefTask = Task.CompletedTask; var taskInstance = new OriginalCode<Task>(Task.Delay(100)); var taskDelegate = (OriginalCode<Task>.PostFix)taskInstance.Postfix().CreateDelegate(typeof(OriginalCode<Task>.PostFix), taskInstance); taskDelegate(ref testRefTask); // testRefTask 现在是Delay(100)的任务
关键修改说明
- DynamicMethod参数列表:新增
OriginalCode<T>类型的参数用于传递实例,确保能访问实例的Value属性。 - IL指令逻辑:
Ldarg_1加载ref参数的内存地址,Ldarg_0加载当前实例。- 通过
OpCodes.Call调用Value的getter方法获取实例属性值。 - 根据
T是否为值类型选择对应的存储指令:值类型用Stobj将值拷贝到目标地址,引用类型用Stind_Ref将引用写入地址。
- 委托绑定:创建委托时传入当前实例,让
DynamicMethod的第一个参数自动填充为实例对象。
内容的提问来源于stack exchange,提问作者NannerBannaner23
相关产品推荐
相关产品推荐

