c# – 按接口动态创建类

当我能够动态生成方法时,我对.Net表达有一些了解.没关系,这很好.

但现在我需要生成一个完整的类,似乎唯一的方法就是Emit整个IL,这是完全不可接受的(它是不可能支持的).

假设我们有以下界面:

public interface IFoo
{
    [Description("5")]
    int Bar();
    [Description("true")]
    bool Baz();
}

应转换为:

public class Foo : IFoo
{
    public int Bar() => 5;
    public bool Baz() => true;
}

我怎样才能实现它?没有第三方工具和库,它甚至可能吗?我知道GitHub上有很多有用的工具,但我真的不想导入一个完整的MVVM框架来生成一些代码.

如果我可以使用表达式,并使用我已经用它生成的方法创建一个类.但是现在我不知道该怎么做.

最佳答案 首先,由于你正在处理远程处理,我不得不提到这是.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();
}

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

在我的示例中,我假设一个无参数方法,但只要您确保LambdaExpression使用与接口方法完全相同的类型,存根就像在序列中执行所有Ldargs一样简单,一个Call和一个Ret.如果你的真实代码(在静态方法中)足够短,它通常会被内联.因为这是一个像其他任何一个的论点,如果你有冒险精神,你可以采取生成的方法的方法体,并将其直接放入虚拟方法 – 请注意你需要在两个通道中这样做但是.

点赞