通过接口动态创建类

时间:2016-07-13 07:41:23

标签: c# .net reflection code-generation expression-trees

当我能够动态生成方法时,我对.Net Expressions有所了解。没关系,这很好。

但是现在我需要生成一个完整的类,似乎唯一的方法是发送整个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框架来生成一些代码。

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

3 个答案:

答案 0 :(得分:1)

您可以使用CodeDOM和Emit。但是,我认为这不值得。

对于类似的事情,我使用以下库: http://www.castleproject.org/projects/dynamicproxy/

即使目标类不可用,也可以创建代理类(然后必须拦截所有方法)。

答案 1 :(得分:1)

首先,由于您正在处理远程处理,我不得不提到.NET最初是为了支持而设计的(从.NET作为COM 2.0返回)。您最直接的解决方案是实现透明的远程处理代理 - 只需创建自己的(可能是通用的)类派生自System.Runtime.Remoting.Proxies.RealProxy,并且您可以通过覆盖{{1}来提供实现所需函数所需的所有逻辑。 }} 方法。使用Invoke,您可以获得实施界面的代理,并且您可以继续使用。

显然,在每次调用期间,这都会在运行时产生成本。但是,除了您根本不进行任何I / O之外,它通常完全不重要,特别是如果您正在处理网络。实际上,除非你处于紧密的循环中,即使不进行I / O操作也是非常不重要的 - 只有性能测试才能真正判断出你是否对成本有好处。< / p>

如果你真的想要预生成所有方法体,而不是在运行时保持逻辑动态,你可以利用GetTransparentProxy给你LambdaExpression的事实。与CompileToMethod不同,你没有得到一个可以直接调用的漂亮的小委托,但它可以让你选择使用lambda表达式来显式构建方法体 - 这反过来又允许你在没有求助的情况下创建整个类委托调用。

完整(但简单)的例子:

Compile

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

在我的示例中,我假设一个无参数方法,但只要您确保this使用与接口方法完全相同的类型,存根就像执行所有{{{1}一样简单。 1}}在一个序列中,一个LambdaExpression和一个Ldarg。如果你的真实代码(在静态方法中)足够短,它通常会被内联。由于Call与其他任何参数都是一个参数,如果你感觉冒险,你可以只使用生成方法的方法体并将其直接放入虚拟方法中 - 请注意你&#39;但是,我需要两次通过。

答案 2 :(得分:1)

我目前实际上正在使用rxjs/operator软件包,该软件包使您可以集成到MSBuild管道中并进行构建。例如,请参阅我的REST swaggersolidity -> C# codegen