簡體   English   中英

System.Reflection.Emit 生成的 Getter 方法無法返回原始類型,但對非原始對象按預期工作

[英]Getter method generated by System.Reflection.Emit fails to return primitive types, but works as expected for non-primitive objects

我正在嘗試編寫一個程序,該程序圍繞具有屬性的現有 class 創建動態“包裝器” class,並將所有虛擬屬性獲取器和設置器重定向到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 的新DataClassWrapper ,它繼承自DataClass ,並使用 IL 代碼覆蓋所有屬性的 getter 和 setter 方法,將調用重定向到基礎 class 中的相應 GetValue 和 SetValue 方法,並傳遞屬性的名稱作為第一個論點。 完成這一切的實際方法對於這篇文章來說有點太長了,但可以在這里查看。

這是為每個屬性生成設置器的代碼:(在所有情況下都能正常工作)

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 中的一個屬性,而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 代碼。 但它似乎只發生在原始類型上。 有人有想法嗎?

我認為你的問題是castclass指令。 文檔說:

typeTok [參數] 是元數據標記(typeref、typedef 或 typespec),指示所需的 class。 如果 typeTok 是不可為空的值類型或泛型參數類型,則將其解釋為“裝箱”的 typeTok。 如果 typeTok 是可空類型 Nullable,則將其解釋為“裝箱”T。

與強制轉換 (§III.1.6) 和轉換 (§III.3.27) 不同,強制轉換永遠不會改變 object 的實際類型並保留 object 標識(參見第 I 部分)

這意味着如果 object 是值類型的裝箱實例,castclass不會將其拆箱,但會保留 object 引用。 所以這實際上會返回 object 的 managed(,) 地址。 這是沒用的,如果是值類型,您需要使用unbox.any指令。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM