[英]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.