【问题标题】:Reflection Emit: How to generate getter for comparision反射发射:如何生成 getter 进行比较
【发布时间】:2017-01-05 04:42:23
【问题描述】:

我在以下代码中为 HasChildren 属性生成 getterMethod 时遇到问题。

有人可以帮我解决吗?

c#代码

 public class Sample
{       
    public ObservableCollection<Sample> Children { get; set; }    
    public bool HasChildren  { get { return Children?.Count() > 0; } }          
}

此处为方法构建器引用了不起作用的代码。

                    const string assemblyName = "HasChildrenAssembly";
        const string childrenProperty = "Children";
        const string hasChildrenProperty = "HasChildren";
        const string typeName = "Sample";
        const string assemblyFileName = assemblyName + ".dll";

        AppDomain domain = AppDomain.CurrentDomain;
        AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(new AssemblyName(assemblyName), AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName, assemblyFileName);
        TypeBuilder typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);

        Type typeOfChildren = typeof(ObservableCollection<>);
        Type genericTypeOfChildren = typeOfChildren.MakeGenericType(typeBuilder);

        FieldBuilder childrenField = typeBuilder.DefineField($"_{childrenProperty}", genericTypeOfChildren, FieldAttributes.Private);
        PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(childrenProperty, PropertyAttributes.None, childrenField.FieldType, Type.EmptyTypes);

        MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
        MethodBuilder getChildrenMethod = typeBuilder.DefineMethod($"get_{propertyBuilder.Name}", getSetAttr, childrenField.FieldType, Type.EmptyTypes);
        ILGenerator il = getChildrenMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldfld, childrenField);
        il.Emit(OpCodes.Ret);
        propertyBuilder.SetGetMethod(getChildrenMethod);

        MethodBuilder setChildrenMethod = typeBuilder.DefineMethod($"set_{propertyBuilder.Name}", getSetAttr, null, new[] { propertyBuilder.PropertyType });         
        il = setChildrenMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Stfld, childrenField);
        il.Emit(OpCodes.Ret);
        propertyBuilder.SetSetMethod(setChildrenMethod);

        MethodInfo countMethodInfo = typeof(System.Linq.Enumerable).GetMethods().Single(method => method.Name == "Count" && method.IsStatic && method.GetParameters().Length == 1);
        propertyBuilder = typeBuilder.DefineProperty(hasChildrenProperty, PropertyAttributes.None, typeof(bool), Type.EmptyTypes);
        MethodBuilder getHasChildrenMethod = typeBuilder.DefineMethod($"get_{hasChildrenProperty}", getSetAttr, propertyBuilder.PropertyType, Type.EmptyTypes);
        il = getHasChildrenMethod.GetILGenerator();
        var notNullLabel = il.DefineLabel();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, getChildrenMethod);
        il.Emit(OpCodes.Dup);
        il.Emit(OpCodes.Brtrue_S, notNullLabel);         
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Ret);
        il.MarkLabel(notNullLabel);

        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, countMethodInfo);
        il.Emit(OpCodes.Ldc_I4_0);
        il.Emit(OpCodes.Cgt);
        il.Emit(OpCodes.Ret);
        propertyBuilder.SetGetMethod(getHasChildrenMethod);

        ConstructorBuilder constructor = typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, new Type[] { typeBuilder });
        il = constructor.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Call, setChildrenMethod);
        il.Emit(OpCodes.Ret);

        Type type = typeBuilder.CreateType();
        var obj1 = Activator.CreateInstance(type, new object[] { null });

        var obj2 = Activator.CreateInstance(type, obj1);

        assemblyBuilder.Save(assemblyFileName);

提前致谢。

【问题讨论】:

  • 你不应该尝试自己编写 IL 代码,让高级编译器为你做这件事。尽管我不确定如果正确发出此 IL 代码序列是否会运行,但至少您错过了声明您正在使用的局部变量并且您的分支指令不完整。
  • 感谢您的评论。有什么可以参考的吗?
  • 查看 ILGenerator 的 msdn,它提供了您所需的一切、正确分支的标签和局部变量的定义。为了快速轻松地生成简单的 IL 块,我可以推荐 tryroslyn.azurewebsites.net。
  • 太棒了!!我会试试的。再次感谢
  • @thehennyy!我试过了,但它似乎不起作用。你能看看 HasChildren 属性的吸气剂吗?

标签: c# reflection reflection.emit typebuilder


【解决方案1】:

您没有在 Count 方法中指定通用参数。

您必须使用MakeGenericMethod 指定泛型参数是Sample

您在notNullLabel 之后还有一个额外的Ldarg_0this 此时已经在堆栈上,这要感谢一开始的 dup 操作。

这是一个稍作修改的版本,可以按您的预期工作:

        AppDomain ad = AppDomain.CurrentDomain;
        AssemblyBuilder ab = ad.DefineDynamicAssembly(new AssemblyName("toto.dll"), AssemblyBuilderAccess.RunAndSave);
        ModuleBuilder mb = ab.DefineDynamicModule("toto.dll");
        TypeBuilder tb = mb.DefineType("toto.Sample", TypeAttributes.Public | TypeAttributes.Class);


        FieldBuilder fb = tb.DefineField("<Children>k__BackingField", typeof(ObservableCollection<>).MakeGenericType(tb), FieldAttributes.Private | FieldAttributes.InitOnly);
        fb.SetCustomAttribute(new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(new Type[0]), new object[0]));

        PropertyBuilder pb = tb.DefineProperty("Children", PropertyAttributes.None, typeof(ObservableCollection<>).MakeGenericType(tb), new Type[0]);

        MethodBuilder getter = tb.DefineMethod("get_Children", MethodAttributes.Public, CallingConventions.HasThis, typeof(ObservableCollection<>).MakeGenericType(tb), new Type[0]);
        getter.SetCustomAttribute(new CustomAttributeBuilder(typeof(CompilerGeneratedAttribute).GetConstructor(new Type[0]), new object[0]));
        ILGenerator ilgen = getter.GetILGenerator();
        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Ldfld, fb);
        ilgen.Emit(OpCodes.Ret);

        pb.SetGetMethod(getter);

        PropertyBuilder pbhas = tb.DefineProperty("HasChildren", PropertyAttributes.None, typeof(bool), new Type[0]);
        MethodBuilder hasgetter = tb.DefineMethod("get_HasChildren", MethodAttributes.Public, CallingConventions.HasThis, typeof(bool), new Type[0]);
        ilgen = hasgetter.GetILGenerator();
        Label notNullLabel = ilgen.DefineLabel();

        ilgen.Emit(OpCodes.Ldarg_0);
        ilgen.Emit(OpCodes.Call, getter);
        ilgen.Emit(OpCodes.Dup);
        ilgen.Emit(OpCodes.Brtrue, notNullLabel);

        ilgen.Emit(OpCodes.Pop);
        ilgen.Emit(OpCodes.Ldc_I4_0);
        ilgen.Emit(OpCodes.Ret);

        ilgen.MarkLabel(notNullLabel);


        MethodInfo mi = typeof(Enumerable).GetMethods()
                                 .Where(m => m.Name == "Count" && m.GetGenericArguments().Length == 1 && m.GetParameters().Length == 1)
                                 .First()
                                 .MakeGenericMethod(tb);

        ilgen.Emit(OpCodes.Call, mi);
        ilgen.Emit(OpCodes.Ldc_I4_0);
        ilgen.Emit(OpCodes.Cgt);

        ilgen.Emit(OpCodes.Ret);

        pbhas.SetGetMethod(hasgetter);

        tb.CreateType();
        ab.Save("toto.dll");

【讨论】:

  • 没有必要使用无条件分支到最后。你可以只发出一个 ret 指令。还可以注意到原始代码中有一个多余的 ldarg.0 指令造成了麻烦。
  • 确实如此。整合您的建议
  • 谢谢 thehennyy 和 Regis-portalez。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-26
  • 1970-01-01
  • 1970-01-01
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多