如何使用代码生成来动态创建 C# 方法?

Posted

技术标签:

【中文标题】如何使用代码生成来动态创建 C# 方法?【英文标题】:How to use code generation to dynamically create C# methods? 【发布时间】:2011-01-22 05:06:42 【问题描述】:

为了在 C 中定义一个可由 Lua 调用的方法,它必须匹配给定的签名并使用 Lua API 来检索参数并返回结果。我正在编写 Lua 的 C# 包装器,并且我对能够调用任意 C# 方法而不使它们遵循这些约定感兴趣。当包装在 D 之类的东西中时,可以使用模板系统为任何给定的方法动态创建此胶合代码。我在想这在 C# 中也是可能的,但是通过使用动态代码生成。

C API 看起来像这样,生成的代码将通过我的库的较低级别部分来操作它,该部分 P/Invokes Lua C 库。

static int foo (lua_State *L)

    int n = lua_gettop(L);    /* number of arguments */
    lua_Number sum = 0;
    int i;
    for (i = 1; i <= n; i++)
    
        if (!lua_isnumber(L, i)) 
        
            lua_pushstring(L, "incorrect argument");
            lua_error(L);
        
        sum += lua_tonumber(L, i);
    
    lua_pushnumber(L, sum/n);        /* first result */
    lua_pushnumber(L, sum);         /* second result */
    return 2;                   /* number of results */

所以基本上这个想法是采用一个 C# 方法,反映它的参数和返回值,生成(或从缓存中检索)一个使用 Lua API 的方法,就像上面一样,传递这些参数并返回这些返回类型,最后推送它Lua 的方法。因此,当从 Lua 调用 C# 函数时,它看起来像 lua -> 魔术包装函数 -> 普通 C# 函数。

谢谢。

【问题讨论】:

【参考方案1】:

如果我明白你想要什么,看来你有两个选择:

    use the CodeDOM 在运行时生成和动态编译代码。 发出实际的 C# 源代码,并在运行时将其动态编译为可调用程序集。

CodeDom 是一种毛茸茸的、非常低级的代码。这个想法是 C# 语言有一个对象模型。您首先实例化一个 CodeTypeDeclaration - 这将生成一个类型或类。然后您添加属性和字段 - 在这里您可能会为您的 p/invoke 函数添加 DllImport 声明。然后您使用不同的 CodeDOM 添加方法到该类型 - 这将是您插入生成的方法的地方。你可以把它公开,静态,任何你喜欢的。

CodeDOM 看起来像这样:

System.Type mt= a[0].GetType();

System.CodeDom.CodeTypeDeclaration class1 = new System.CodeDom.CodeTypeDeclaration(mt.Name);
class1.IsClass=true;
class1.TypeAttributes = System.Reflection.TypeAttributes.Public;
class1.Comments.Add(new System.CodeDom.CodeCommentStatement("Wrapper class for " + mt.Name));

System.CodeDom.CodeConstructor ctor;
ctor= new System.CodeDom.CodeConstructor();
ctor.Attributes = System.CodeDom.MemberAttributes.Public;
ctor.Comments.Add(new System.CodeDom.CodeCommentStatement("the null constructor"));
class1.Members.Add(ctor);
ctor.Statements.Add(new System.CodeDom.CodeAssignStatement(new System.CodeDom.CodeVariableReferenceExpression("m_wrapped"), new System.CodeDom.CodeObjectCreateExpression(mt)));

ctor= new System.CodeDom.CodeConstructor();
ctor.Attributes = System.CodeDom.MemberAttributes.Public;
ctor.Comments.Add(new System.CodeDom.CodeCommentStatement("the 'copy' constructor"));
class1.Members.Add(ctor);
ctor.Parameters.Add(new System.CodeDom.CodeParameterDeclarationExpression(mt,"X"));
ctor.Statements.Add(new System.CodeDom.CodeAssignStatement(new System.CodeDom.CodeVariableReferenceExpression("m_wrapped"), new System.CodeDom.CodeVariableReferenceExpression("X")));

// embed a local (private) copy of the wrapped type
System.CodeDom.CodeMemberField field1;
field1= new System.CodeDom.CodeMemberField();
field1.Attributes = System.CodeDom.MemberAttributes.Private;
field1.Name= "m_wrapped";
field1.Type=new System.CodeDom.CodeTypeReference(mt);
class1.Members.Add(field1);

...

它继续。等等。如您所见,它变得非常丑陋。然后你编译它,我没有显示。我假设你不想采用这种方法。


我发现 CodeDom 使用起来非常笨拙;相反,现在当我需要动态生成的程序集时,我会将实际的 C# 代码(通常通过模板)发送到内存中的字符串中,然后编译 那个。就我的目的而言,这要简单得多。编译如下:

var cp = new System.CodeDom.Compiler.CompilerParameters 
  ReferencedAssemblies.Add(filesystemLocation), // like /R: option on csc.exe
  GenerateInMemory = true,    // you will get a System.Reflection.Assembly back
  GenerateExecutable = false, // Dll
  IncludeDebugInformation = false,
  CompilerOptions = ""
;

var csharp = new Microsoft.CSharp.CSharpCodeProvider();

// this actually runs csc.exe:
System.CodeDom.Compiler.CompilerResults cr = 
      csharp.CompileAssemblyFromSource(cp, LiteralSource);


// cr.Output contains the output from the command

if (cr.Errors.Count != 0)

    // handle errors


System.Reflection.Assembly a = cr.CompiledAssembly;

// party on the type here, either via reflection...
System.Type t = a.GetType("TheDynamicallyGeneratedType");

// or via a wellknown interface

在上面的代码中,LiteralSource 包含要编译的源代码。正如我所说,我通过阅读模板并填写空白来生成它。

【讨论】:

这是一种有趣的方法,我从没想过编写实际的 C# 然后编译它。大概我最终会做什么。谢谢。 首先如何在磁盘上生成类? @jcolebrand - 我不清楚你在问什么,但我认为你应该发布一个新问题。 是的,但这显然是一个棘手的对话。 chat.***.com/transcript/message/2889886#2889886我已经尝试过如何询问,如果你想看看我有什么,也许可以参与对话【参考方案2】:

我不确定我是否正确解释了您的问题,但您可能想看看 Castle.Dynamic 代理。它允许您为类和接口创建代理,然后拦截某些方法调用(接口上的任何内容以及真实类上的任何虚拟内容)。当您拦截调用时,您只需查看参数并通过 P-Invoke 将调用转发到 lua API。有一个很棒的教程here。

【讨论】:

这很有趣,我一定会调查的。我认为这对于从 C# 调用 Lua 方法更有用,而我想从 Lua 调用 C# 方法。【参考方案3】:

您可以将 C# 公开为 COM,这将允许将所有(公共)方法调用为外部应用程序。

或者,公开一个单独的 C# 函数,该函数将调用适当的其他函数,可能为 C# 中的实际函数列表硬编码,或者可能使用反射。它可能需要一个任意大小的参数数组。

【讨论】:

使用这样的反射几乎是我想要避免的。【参考方案4】:

尝试查看T4。因为它本身是 Visual Studio 的一部分,所以您可以使用反射框架根据您的问题查找所有方法。在 google 上搜索,我相信您可以找到一些示例代码或模板,这些示例代码或模板已经使用 T4 反射来生成包装器类或方法。

【讨论】:

这不解决在运行时从 LUA(或任何语言)动态调用任意 C# 函数的问题 您可以使用 T4 生成包装器类,并最终获得与在接受的答案中使用 CodeDOM 相同的结果。这不是让 Lua 能够动态调用任意方法。所以我不理解你的关注和反对! "...我对能够调用任意 C# 方法而不使它们遵循这些约定很感兴趣..."

以上是关于如何使用代码生成来动态创建 C# 方法?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 LINQ SQL C# 中返回动态周数?

如何从 CUDA C++ 创建和使用动态库“.so”并在 Linux 环境 (CentOS) 下的 C# 代码中使用它?

C# 方法通过字符串动态生成表单

使用 Linq 表达式进行 C# 动态数据库过滤

如何使用 JPQL 创建动态更新查询?

C#动态创建接口的实现实例对象