【问题标题】:How to load an object to stack in Reflection.Emit C#如何在 Reflection.Emit C# 中加载要堆叠的对象
【发布时间】:2016-09-19 10:07:08
【问题描述】:

我正在尝试将此workaround 调整到我的项目中。而不是string,我想给基本构造函数一个objectRevitFamily 类型)。

不幸的是,我不知道如何使用Reflection.Emitobjectobject 引用加载到堆栈中。谁能帮我?

到目前为止,我的代码看起来像这样,但我无法将object 传递给gen.Emit(opcode, _)

TypeBuilder tb = mb.DefineType(newtypename, new TypeAttributes(), typeof(LoadFamily));
var ci = typeof(LoadFamily).GetConstructor(new[] { typeof(RevitFamily) });
var constructorBuilder = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
var gen = constructorBuilder.GetILGenerator();
gen.DeclareLocal(typeof(RevitFamily));

Label ilLabel = gen.DefineLabel();
gen.Emit(OpCodes.Ldarg_0);                // Load "this" onto eval stack
// Here I should some how get he object f in the stack....
gen.Emit(OpCodes.Ldloca, f);              // Load pointer to RevitFamily on stack
gen.Emit(OpCodes.Call, ci);               // call base constructor (consumes "this" and the string)
gen.Emit(OpCodes.Nop);                    // Fill some space - this is how it is generated for equivalent C# code
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Nop);
gen.Emit(OpCodes.Ret);                    // return from constructor
t = tb.CreateType();

应该用类似这样的构造函数来创建动态类型:

public class LoadconcreteFamily : LoadFamily
{
    public LoadconcreteFamily() : base(f)
    { }
}

【问题讨论】:

  • 您是否已经在某处拥有所需类型的对象,或者您想创建一个新对象然后传递它?
  • 该代码不会自行编译,因为f 没有在任何地方声明。出于同样的原因,您在发出 IL 时遇到了麻烦。您无法加载对在堆栈上生成的代码 之外创建的本地对象的引用。也可以生成代码来创建对象,或者从字段加载引用,或者从传递给生成方法的参数加载引用。一旦您将对象存储在某个字段、本地或参数中,通常的ldarg/ldfld 操作码就会应用。
  • @thehennyy: 是的,f 是一个对象,以这段代码段为参数赋予函数。
  • @JeroenMostert:谢谢...。我想,我明白了,为什么我想要达到的目标不能完成。

标签: c# reflection cil revit-api emit


【解决方案1】:

为了(几乎)实现您的要求,您必须按照 cmets 中的建议从其他地方(可能是单例等的字段)加载对象。

为什么?

因为您在 IL 中尝试执行的操作也无法在 C# 中完成。你问什么

public class LoadconcreteFamily : LoadFamily
{
    public LoadconcreteFamily() : base(f)
    { }
}

不是有效的 C# 代码,因为 f 并非来自任何地方。事实上,f 是一个文字,而不是对方法、属性或字段的引用。 构造函数只能看到传递给调用者的static instances or arguments

之所以这样定义语法,是因为是一个运行时概念,与你的代码如何运行有关,而类定义是一个设计时概念,描述元素来解决一个特定的问题。 当然它们是相关的,但这里的主要概念是类本身是不需要堆栈的抽象[1]

因此,首先您必须修复您的高级代码。我在这里看到两种方法:

第一种方法将参数添加到构造函数:

public class LoadConcreteFamily : LoadFamily
{
    public LoadConcreteFamily(RevitFamily f) : base(f)
    { }
}

因此,您的类型定义将包含新参数,并且签名与您要求的明显不同,但代码是自包含的,不使用全局上下文。

第二种方法有一个全局实例来保存您的数据:

public static class FamilyContainer
{
    public static RevitFamily SharedFamily { get; private set; }

    public static void InitializeFamily(RevitFamily family)
    {
        if (family == null)
            throw new ArgumentNullException(nameof(family));

        SharedFamily = family;
    }
}

public class LoadConcreteFamily : LoadFamily
{
    public LoadConcreteFamily() : base(FamilyContainer.SharedFamily)
    {

    }
}

应该是(未经测试的):

TypeBuilder tb = mb.DefineType(newTypeName, new TypeAttributes(), typeof(LoadFamily));
var ci = typeof(LoadFamily).GetConstructor(new[] { typeof(RevitFamily) });
var constructorBuilder = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]);
var gen = constructorBuilder.GetILGenerator();
gen.DeclareLocal(typeof(RevitFamily));

gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Call, typeof(FamilyContainer).GetProperty(nameof(FamilyContainer.SharedFamily), BindingFlags.Public | BindingFlags.Static).GetGetMethod());
gen.Emit(OpCodes.Call, ci);
gen.Emit(OpCodes.Ret);

t = tb.CreateType();

恕我直言,应仔细评估静态实例的引入,因为全局变量会为您的代码引入意外和不可预见的依赖关系,从而使其维护和调试更加复杂。

即使不是特定于 C#,阅读这个 SO 问题 Are global variables bad? 可能也会受益。

出于这个原因,我会采用第一种方法。

[1]为了简洁起见,我省略了几个细节,并且可能过度简化概念本身可能会更困难

【讨论】:

    猜你喜欢
    • 2014-12-29
    • 2016-09-02
    • 2010-11-11
    • 2010-12-28
    • 1970-01-01
    • 2011-11-24
    • 2014-10-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多