You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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属性。


解决方案

核心问题分析

  1. 原代码使用OpCodes.Ldstr硬编码加载字符串常量,仅适用于string类型,无法处理其他类型的Value。
  2. DynamicMethod未关联当前OriginalCode<T>实例,无法获取实例的Value属性值。
  3. 未区分值类型和引用类型的存储指令,导致值类型赋值失败。

修正后的代码

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)的任务

关键修改说明

  1. DynamicMethod参数列表:新增OriginalCode<T>类型的参数用于传递实例,确保能访问实例的Value属性。
  2. IL指令逻辑:
    • Ldarg_1加载ref参数的内存地址,Ldarg_0加载当前实例。
    • 通过OpCodes.Call调用Value的getter方法获取实例属性值。
    • 根据T是否为值类型选择对应的存储指令:值类型用Stobj将值拷贝到目标地址,引用类型用Stind_Ref将引用写入地址。
  3. 委托绑定:创建委托时传入当前实例,让DynamicMethod的第一个参数自动填充为实例对象。

内容的提问来源于stack exchange,提问作者NannerBannaner23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 23:47:36