通过接口动态创建类

Ale*_*kiy 6 .net c# reflection code-generation expression-trees

.Net Expressions当我能够动态生成方法时,我有一些经验。很好,很好。

但是现在我需要生成一个完整的类,并且看来唯一的方法就是发出整个IL,这是完全不能接受的(无法支持)。

假设我们有以下界面:

public interface IFoo
{
    [Description("5")]
    int Bar();
    [Description("true")]
    bool Baz();
}
Run Code Online (Sandbox Code Playgroud)

应转换为:

public class Foo : IFoo
{
    public int Bar() => 5;
    public bool Baz() => true;
}
Run Code Online (Sandbox Code Playgroud)

我该如何实现?如果没有第三方工具和库,是否有可能?我知道GitHub上有很多有用的utils,但是我真的不想导入整个MVVM框架来生成一些代码。

如果可以使用Expressions,并使用已经使用它生成的方法创建一个类。但是目前我还不知道该怎么做。

Lua*_*aan 5

首先,由于您正在处理远程处理,所以我必须提到,.NET 最初是从头开始设计支持这种功能的(从 .NET 的根源 COM 2.0 开始)。最直接的解决方案是实现一个透明的远程代理 - 只需创建您自己的(可能是通用的)类,派生自System.Runtime.Remoting.Proxies.RealProxy,并且您可以通过重写该方法来提供实现您需要的任何功能所需的所有逻辑Invoke。使用GetTransparentProxy,您可以获得实现您的接口的代理,然后就可以开始了。

显然,这在运行时、每次调用期间都会产生成本。然而,与进行任何 I/O 相比,这通常完全不重要,尤其是在处理网络时。事实上,除非你处于一个紧密的循环中,否则即使不执行 I/O,它也非常不重要 - 只有性能测试才能真正告诉你是否可以接受成本。

如果您确实想预生成所有方法体,而不是在运行时保持逻辑动态,则可以利用为LambdaExpression您提供CompileToMethod. 与 不同的是Compile,您没有得到一个可以直接调用的漂亮的小委托,但它为您提供了使用 lambda 表达式显式构建方法体的选项 - 这反过来又允许您创建整个类而无需诉诸委托调用。

一个完整(但简单)的例子:

void Main()
{
  var ab = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAssembly"), AssemblyBuilderAccess.Run);
  var mb = ab.DefineDynamicModule("Test");

  var tb = mb.DefineType("Foo");
  tb.AddInterfaceImplementation(typeof(IFoo));

  foreach (var imethod in typeof(IFoo).GetMethods())
  {
    var valueString = ((DescriptionAttribute)imethod.GetCustomAttribute(typeof(DescriptionAttribute))).Description;

    var method = 
      tb.DefineMethod
      (
        "@@" + imethod.Name, 
        MethodAttributes.Private | MethodAttributes.Static, 
        imethod.ReturnType,
        new [] { tb }
      );

    // Needless to say, I'm making a lot of assumptions here :)
    var thisParameter = Expression.Parameter(typeof(IFoo), "this");

    var bodyExpression =
      Expression.Lambda
      (
        Expression.Constant
        (
          Convert.ChangeType(valueString, imethod.ReturnType)
        ),
        thisParameter
      );

    bodyExpression.CompileToMethod(method);

    var stub =
      tb.DefineMethod(imethod.Name, MethodAttributes.Public | MethodAttributes.Virtual, imethod.ReturnType, new Type[0]);

    var il = stub.GetILGenerator();
    il.Emit(OpCodes.Ldarg_0);
    il.EmitCall(OpCodes.Call, method, null);
    il.Emit(OpCodes.Ret);

    tb.DefineMethodOverride(stub, imethod);
  }

  var fooType = tb.CreateType();
  var ifoo = (IFoo)Activator.CreateInstance(fooType);

  Console.WriteLine(ifoo.Bar()); // 5
  Console.WriteLine(ifoo.Baz()); // True
}

public interface IFoo
{
    [Description("5")]
    int Bar();
    [Description("true")]
    bool Baz();
}
Run Code Online (Sandbox Code Playgroud)

如果您曾经使用过 .NET 发出,那么这应该非常简单。我们定义动态程序集、模块、类型(理想情况下,您希望在单个动态程序集中一次定义所有类型)。棘手的部分是Lambda.CompileToMethod它只支持静态方法,所以我们需要作一点欺骗。首先,我们创建一个静态方法作为this参数并在那里编译 lamdba 表达式。然后,我们创建一个方法存根 - 一段简单的 IL,确保正确调用我们的静态方法。最后,我们将接口方法绑定到存根。

在我的示例中,我假设使用无参数方法,但只要确保使用LambdaExpression与接口方法完全相同的类型,存根就像Ldarg按顺序执行所有 s 一样简单,单个Call和单个Ret。如果您的实际代码(在静态方法中)足够短,它通常会被内联。由于this是一个像任何其他参数一样的参数,如果您喜欢冒险,您可以直接获取生成方法的方法主体并将其直接放入虚拟方法中 - 请注意,您需要分两遍执行此操作,尽管。


Ale*_*kiy 1

实际上,我目前正在使用CodeGeneration.Roslyn允许您集成到 MSBuild 管道并构建内容的包。例如,请参阅我的REST swaggerSolidity -> C# codegen