Getter 由 System.Reflection.Emit 生成的方法无法 return 基本类型,但对非基本对象按预期工作
Getter method generated by System.Reflection.Emit fails to return primitive types, but works as expected for non-primitive objects
我正在尝试编写一个程序,围绕现有的 class 属性创建动态“包装器”class,并重定向所有虚拟属性 getters 和 setters 到 BaseClass
中的专用 GetValue
和 SetValue
方法。解释起来有点困难,所以这里是目前的代码:
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
的新 class,它继承自 DataClass
,并覆盖了所有属性的 getter 和 setter 方法与 IL 代码将调用重定向到基础 class 中的相应 GetValue 和 SetValue 方法,将 属性 的名称作为第一个参数传递。做这一切的实际方法对于这个post有点太长了,但是可以查看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
引用了基础 class 中的 SetValue()
方法。
Getter:
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);
我可以毫无问题地读写属性 StrValue
和 FloatListValue
,我的自定义 GetValue
和 SetValue
方法正在按应有的方式被调用。
然而,如果我尝试读取像 BoolValue
或 IntValue
这样的原始 属性,它 returns 一个看似 运行dom 垃圾数字(例如-1151033224 我最后一次 运行 这个代码)。如果我将 IntValue
转换为可为空的 IntValue?
值,它仍然是 returns 乱码,但这次 运行dom 数字要小得多(在 0-700 运行ge).
setter 方法确实正确接收了原始的 int 值,getter 也完整地检索了它,所以问题一定出在调用 getter 的生成的 IL 代码周围.但这似乎只发生在原始类型上。有人有想法吗?
我认为您的问题是 castclass
指令。文档说:
typeTok [the argument] is a metadata token (a typeref, typedef or
typespec), indicating the desired class. If typeTok is a non-nullable
value type or a generic parameter type it is interpreted as “boxed”
typeTok. If typeTok is a nullable type, Nullable, it is interpreted
as “boxed” T.
Unlike coercions (§III.1.6) and conversions (§III.3.27), a cast never changes the actual type of an object and preserves object identity (see Partition I)
这意味着如果对象是值类型的装箱实例,castclass 不会取消装箱但保留对象引用。所以这实际上将 return 对象的托管(!)地址,这是无用的。如果是值类型,则需要使用 unbox.any
指令。
我正在尝试编写一个程序,围绕现有的 class 属性创建动态“包装器”class,并重定向所有虚拟属性 getters 和 setters 到 BaseClass
中的专用 GetValue
和 SetValue
方法。解释起来有点困难,所以这里是目前的代码:
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
的新 class,它继承自 DataClass
,并覆盖了所有属性的 getter 和 setter 方法与 IL 代码将调用重定向到基础 class 中的相应 GetValue 和 SetValue 方法,将 属性 的名称作为第一个参数传递。做这一切的实际方法对于这个post有点太长了,但是可以查看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
引用了基础 class 中的 SetValue()
方法。
Getter:
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);
我可以毫无问题地读写属性 StrValue
和 FloatListValue
,我的自定义 GetValue
和 SetValue
方法正在按应有的方式被调用。
然而,如果我尝试读取像 BoolValue
或 IntValue
这样的原始 属性,它 returns 一个看似 运行dom 垃圾数字(例如-1151033224 我最后一次 运行 这个代码)。如果我将 IntValue
转换为可为空的 IntValue?
值,它仍然是 returns 乱码,但这次 运行dom 数字要小得多(在 0-700 运行ge).
setter 方法确实正确接收了原始的 int 值,getter 也完整地检索了它,所以问题一定出在调用 getter 的生成的 IL 代码周围.但这似乎只发生在原始类型上。有人有想法吗?
我认为您的问题是 castclass
指令。文档说:
typeTok [the argument] is a metadata token (a typeref, typedef or typespec), indicating the desired class. If typeTok is a non-nullable value type or a generic parameter type it is interpreted as “boxed” typeTok. If typeTok is a nullable type, Nullable, it is interpreted as “boxed” T.
Unlike coercions (§III.1.6) and conversions (§III.3.27), a cast never changes the actual type of an object and preserves object identity (see Partition I)
这意味着如果对象是值类型的装箱实例,castclass 不会取消装箱但保留对象引用。所以这实际上将 return 对象的托管(!)地址,这是无用的。如果是值类型,则需要使用 unbox.any
指令。