【问题标题】:Getter method generated by System.Reflection.Emit fails to return primitive types, but works as expected for non-primitive objectsSystem.Reflection.Emit 生成的 Getter 方法无法返回原始类型,但对非原始对象按预期工作
【发布时间】:2021-12-29 19:40:40
【问题描述】:

我正在尝试编写一个程序,该程序围绕具有属性的现有类创建动态“包装器”类,并将所有虚拟属性 getter 和 setter 重定向到 BaseClass 中的专用 GetValueSetValue 方法.这有点难以解释,所以这里是到目前为止的代码:

public abstract class BaseClass
{
    protected void SetValue(string propertyName, object value) { ... }
    protected object? GetValue(string propertyName) { ... }
}

public class DataClass : BaseClass
{
    public virtual string? StrValue { get; set; }
    public virtual int IntValue { get; set; }
    public virtual bool BoolValue { get; set; }
    public virtual List<float>? FloatListValue { get; set; }
}

在此示例中,我的代码生成一个名为 DataClassWrapper 的新类,该类继承自 DataClass,并使用 IL 代码覆盖所有属性的 getter 和 setter 方法,将调用重定向到相应的 GetValue 和 SetValue 方法基类,将属性名称作为第一个参数传递。做这一切的实际方法对于这篇文章来说有点太长了,但可以查看here

下面是为每个属性生成 setter 的代码:(在所有情况下都能正常工作)

setMethodGenerator.Emit(OpCodes.Ldarg_0); // 'this'
setMethodGenerator.Emit(OpCodes.Ldstr, propertyInfo.Name); // 1st argument of SetValue
setMethodGenerator.Emit(OpCodes.Ldarg_1); // value passed into this setter, aka. `value`
setMethodGenerator.Emit(OpCodes.Box, propertyInfo.PropertyType); // cast it to `object`
setMethodGenerator.EmitCall(OpCodes.Call, baseSetterMethod, Type.EmptyTypes); // call SetValue
setMethodGenerator.Emit(OpCodes.Ret); // return

其中propertyInfo 描述来自DataClass 的属性,baseSetterMethod 引用来自基类的SetValue() 方法。

吸气剂:

getMethodGenerator.Emit(OpCodes.Ldarg_0); // 'this'
getMethodGenerator.Emit(OpCodes.Ldstr, propertyInfo.Name); // 1st (and only) argument of GetValue
getMethodGenerator.EmitCall(OpCodes.Call, baseGetterMethod, Type.EmptyTypes); // call GetValue
getMethodGenerator.Emit(OpCodes.Castclass, propertyInfo.PropertyType); // cast result to expected type
getMethodGenerator.Emit(OpCodes.Ret); // return it

还有一些用法示例:

var type = CreateDynamicType(typeof(DataClass), baseGetterMethod, baseSetterMethod);
var inst = (DataClass) Activator.CreateInstance(type)!;
inst.IntValue = 1123;
Console.Out.WriteLine(inst.IntValue);

我可以毫无问题地读取和写入属性StrValueFloatListValue,我的自定义GetValueSetValue 方法正按应有的方式调用。

但是,如果我尝试读取像 BoolValueIntValue 这样的原始属性,它会返回一个看似随机的垃圾编号(例如,我上次运行此代码时的 -1151033224)。如果我将 IntValue 转换为可空的 IntValue? 值,它仍然会返回乱码,但这次随机数要小得多(在 0-700 范围内)。

setter 方法确实正确地接收到了原始 int 值,而 getter 也原封不动地检索它,所以问题必须围绕生成的调用 getter 的 IL 代码。但它似乎只发生在原始类型上。有人有想法吗?

【问题讨论】:

    标签: c# reflection.emit .net-6.0


    【解决方案1】:

    我认为您的问题是 castclass 指令。文档说:

    typeTok [参数] 是元数据标记(typeref、typedef 或 typespec),表示所需的类。如果 typeTok 是不可为空的 值类型或泛型参数类型,它被解释为“装箱” 打字机。如果 typeTok 是一个可为空的类型,Nullable,它被解释 作为“盒装”T。

    与强制转换 (§III.1.6) 和转换 (§III.3.27) 不同,强制转换永远不会改变对象的实际类型并保留对象身份(参见第 I 部分)

    这意味着如果对象是值类型的装箱实例,castclass 将其拆箱,但保留对象引用。所以这实际上会返回对象的托管(!)地址,这是无用的。如果是值类型,则需要使用unbox.any 指令。

    【讨论】:

    • 是的,这就是问题所在。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-17
    相关资源
    最近更新 更多