[英]Replace Field with Property using System.Reflection.Emit namespace?
[英]System.Reflection.Emit custom property seems to be identical to my mock up, but I get an InvalidProgramException
為了擴展標題,我使用反射來生成一個自定義子類,該子類最終將具有任意數量的字符串屬性,它們將在內部存儲在字典中。 在這種情況下,我只使用一個。 我已經使用 Ildasm.exe 來獲取 My Set 方法所需的 MSIL,因為調試器顯示了我正在分配的值,但是當我嘗試讀回它時,我得到一個 InvalidProgramException,“檢測到公共語言運行時一個無效的程序。” 它指向 get 方法。 我的獲取方法 model 是:
/* public class TestWrapper : AttributeWrapper //This is the source of the following MSIL
{
public string Name
{
get { return GetAttribute("Name"); }
set { SetAttribute("Name", value); }
}
}
*/
{
// Code size 17 (0x11)
.maxstack 2
.locals init ([0] string V_0)
IL_0000: nop
IL_0001: ldarg.0
IL_0002: ldstr "Name"
IL_0007: call instance string ConfigXMLParser.frmNodeBuilder/AttributeWrapper::GetAttribute(string)
IL_000c: stloc.0
IL_000d: br.s IL_000f
IL_000f: ldloc.0
IL_0010: ret
} // end of method TestWrapper::get_Name
生成相關屬性的代碼是:
public static void CreateSelfNamingProperty(TypeBuilder tb, string propertyName, Type propertyType)
{
FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
MethodBuilder getPropMthdBldr =
tb.DefineMethod("get_" + propertyName,
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
typeof(string), Type.EmptyTypes);
Type[] getAttributeArgs = { typeof(string) };
Type basetype = tb.BaseType;
MethodInfo getAttrBase = basetype.GetMethod("GetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(getAttrBase != null);
ILGenerator ilGen = getPropMthdBldr.GetILGenerator();
Label labelReturn = ilGen.DefineLabel();
ilGen.Emit(OpCodes.Nop); //The Get function starts here.
ilGen.Emit(OpCodes.Ldarg_0);
ilGen.Emit(OpCodes.Ldstr, "Nope.");
ilGen.EmitCall(OpCodes.Call, getAttrBase, getAttributeArgs);
ilGen.Emit(OpCodes.Stloc_0);
ilGen.Emit(OpCodes.Br_S, labelReturn);
ilGen.MarkLabel(labelReturn);
ilGen.Emit(OpCodes.Ldloc_0);
ilGen.Emit(OpCodes.Ret);
MethodBuilder setPropMthdBldr =
tb.DefineMethod("set_" + propertyName,
MethodAttributes.Public |
MethodAttributes.SpecialName |
MethodAttributes.HideBySig,
null, new[] { propertyType });
MethodInfo setAttrBase
= basetype.GetMethod("SetAttribute", BindingFlags.Instance | BindingFlags.NonPublic);
ILGenerator setIl = setPropMthdBldr.GetILGenerator();
Label modifyProperty = setIl.DefineLabel();
Label exitSet = setIl.DefineLabel();
Type[] setParamTypes = { typeof(string) , typeof(string) };
setIl.MarkLabel(modifyProperty);
setIl.Emit(OpCodes.Nop); //The Set method starts here
setIl.Emit(OpCodes.Ldarg_0);
setIl.Emit(OpCodes.Ldstr, propertyName);
setIl.Emit(OpCodes.Ldarg_1);
setIl.EmitCall(OpCodes.Call, setAttrBase, setParamTypes);
setIl.Emit(OpCodes.Nop);
setIl.MarkLabel(exitSet);
setIl.Emit(OpCodes.Ret);
propertyBuilder.SetGetMethod(getPropMthdBldr);
propertyBuilder.SetSetMethod(setPropMthdBldr);
}
// And this is what builds the TypeBuilder:
public static TypeBuilder GetTypeBuilder()
{
string typeSignature = "MyDynamicType";
AssemblyName an = new AssemblyName(typeSignature);
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
TypeBuilder tb = moduleBuilder.DefineType(typeSignature,
TypeAttributes.Public |
TypeAttributes.Class |
TypeAttributes.AutoClass |
TypeAttributes.AnsiClass |
TypeAttributes.BeforeFieldInit |
TypeAttributes.AutoLayout,
null);
return tb;
}
最后,把它們放在一起:
TypeBuilder tb = GetTypeBuilder("TestType");
CreateSelfNamingProperty(tb, "Name", typeof(string));
dynamic instance = Activator.CreateInstance(tb.CreateType());
instance.Name = "Test"; //Debug shows Name is Test, but
MessageBox.Show(instance.Name);//Exception occurs here
基礎 class 非常簡單:
public class AttributeWrapper
{
protected Dictionary<string, string> _attributes =
new Dictionary<string, string>();
protected void SetAttribute(string attribute, string value)
{
if (_attributes.ContainsKey(attribute))
{
_attributes[attribute] = value;
}
else
{
_attributes.Add(attribute, value);
}
}
protected string GetAttribute(string attribute)
{
return _attributes.ContainsKey(attribute) ? _attributes[attribute] : "";
}
}
您的直接問題是您正在使用stloc.0
和ldloc.0
操作碼讀取/寫入本地,而沒有定義任何本地:您可以通過在方法頂部調用以下內容來解決此問題:
ilGen.DeclareLocal(typeof(string));
現在事情就是這樣,本地並不是真正需要的。 您反匯編並用作模板的代碼顯然是在Debug
模式下編譯的。 我可以說這是由於當地的原因,但主要來自nop
的。 這兩件事存在於調試版本中以幫助調試過程,允許您介入並查看中間值。 您的吸氣劑可以簡化為以下 IL:
ldarg.0
ldstr "Nope."
call instance string [AttributeWrapper]::GetAttribute(string)
ret
同樣,您的 setter 可以刪除其nop
。
其他一些旁注:
您使用錯誤的方法來發出call
指令。 EmitCall
方法僅用於調用varargs
方法,並接受包含varargs
參數類型的參數。 這不是你在這里所擁有的。 這要么涉及使用VarCall
約定和/或TypedReference
/ __makeref
和__arglist
/ ArgIterator
對 API 進行一些 p/invoke。 后者是“隱藏的” C# 關鍵字,您幾乎不會在代碼中找到它們。 早在 .NET 2.0 之前的日子里,當目標MethodInfo
不是varargs
時,該方法引發異常,但現在情況不再如此。
您應該改用普通的Emit
方法並傳遞適當的Call
或CallVirt
* OpCode
。
最后,我強烈建議為此使用SharpLab ,特別是將其設置為Release
build 並查看 IL 選項卡。 這比編譯代碼然后手動反匯編要容易得多。
* 通常你會看到人們使用后者,即使方法不是virtual
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.