通过接口动态创建一个class
Dynamically create a class by interface
当我能够动态生成方法时,我对 .Net Expressions
有一些经验。很好,很好。
但是现在我需要生成一个完整的class,而且似乎唯一的办法就是Emit whole IL,这是完全不能接受的(不可能支持)。
假设我们有以下接口:
public interface IFoo
{
[Description("5")]
int Bar();
[Description("true")]
bool Baz();
}
应转换为:
public class Foo : IFoo
{
public int Bar() => 5;
public bool Baz() => true;
}
如何实现?没有第 3 方工具和库是否有可能?我知道 GitHub 上有很多有用的实用程序,但我真的不想导入整个 MVVM 框架来生成一些代码。
如果我可以只使用 Expressions
,并使用我已经生成的方法创建一个 class。但是现在我不知道该怎么做。
您可以使用 CodeDOM 和 Emit。但是,我觉得不值得。
对于类似的事情,我正在使用以下库:
http://www.castleproject.org/projects/dynamicproxy/
即使目标 class 不可用,它也能够创建代理 classes(那时你必须拦截所有方法)。
首先,由于您处理的是远程处理,我不得不提一下,这是 .NET 最初设计用于支持的东西(从 .NET 的根源 COM 2.0 开始)。您最直接的解决方案是实现一个透明的远程代理 - 只需制作您自己的(可能是通用的)class 从 System.Runtime.Remoting.Proxies.RealProxy
派生,您可以通过覆盖提供实现您需要的任何功能所需的所有逻辑Invoke
方法。使用 GetTransparentProxy
,您将获得实现接口的代理,一切顺利。
显然,这在每次调用期间都会在运行时产生成本。但是,与您正在制作任何 I/O 这一事实相比,这通常完全不重要,尤其是在您处理网络时。事实上,除非你处于一个紧密的循环中,否则即使不做也很不重要 I/O - 只有性能测试才能真正判断你是否接受成本。
如果你真的想预生成所有的方法体,而不是在运行时保持逻辑动态,你可以利用 LambdaExpression
给你的事实 CompileToMethod
。与 Compile
不同,您没有获得可以直接调用的漂亮的小委托,但它让您可以选择使用 lambda 表达式显式构建方法体——这反过来又允许您创建整个 class es 无需诉诸委托调用。
一个完整(但简单)的例子:
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 emits,这应该非常简单。我们定义一个动态程序集、模块、类型(理想情况下,您希望在单个动态程序集中一次定义所有类型)。棘手的部分是 Lambda.CompileToMethod
只支持静态方法,所以我们需要作弊。首先,我们创建一个以 this
作为参数的静态方法,并在那里编译 lamdba 表达式。然后,我们创建一个方法存根——一个简单的 IL,确保我们的静态方法被正确调用。最后,我们将接口方法绑定到存根上。
在我的例子中,我假设了一个无参数的方法,但是只要你确保 LambdaExpression
使用与接口方法完全相同的类型,存根就像做所有的一样简单Ldarg
一个序列,一个 Call
和一个 Ret
。如果你的真实代码(在静态方法中)足够短,它通常会被内联。由于 this
是一个和其他任何参数一样的参数,如果你喜欢冒险,你可以直接将生成的方法的方法体放入虚拟方法中 - 请注意你需要做不过,这是两次通过。
我目前实际上正在使用 CodeGeneration.Roslyn
包,它允许您集成到 MSBuild 管道并构建东西。例如,参见我的 REST swagger or solidity -> C# codegen.
当我能够动态生成方法时,我对 .Net Expressions
有一些经验。很好,很好。
但是现在我需要生成一个完整的class,而且似乎唯一的办法就是Emit whole IL,这是完全不能接受的(不可能支持)。
假设我们有以下接口:
public interface IFoo
{
[Description("5")]
int Bar();
[Description("true")]
bool Baz();
}
应转换为:
public class Foo : IFoo
{
public int Bar() => 5;
public bool Baz() => true;
}
如何实现?没有第 3 方工具和库是否有可能?我知道 GitHub 上有很多有用的实用程序,但我真的不想导入整个 MVVM 框架来生成一些代码。
如果我可以只使用 Expressions
,并使用我已经生成的方法创建一个 class。但是现在我不知道该怎么做。
您可以使用 CodeDOM 和 Emit。但是,我觉得不值得。
对于类似的事情,我正在使用以下库: http://www.castleproject.org/projects/dynamicproxy/
即使目标 class 不可用,它也能够创建代理 classes(那时你必须拦截所有方法)。
首先,由于您处理的是远程处理,我不得不提一下,这是 .NET 最初设计用于支持的东西(从 .NET 的根源 COM 2.0 开始)。您最直接的解决方案是实现一个透明的远程代理 - 只需制作您自己的(可能是通用的)class 从 System.Runtime.Remoting.Proxies.RealProxy
派生,您可以通过覆盖提供实现您需要的任何功能所需的所有逻辑Invoke
方法。使用 GetTransparentProxy
,您将获得实现接口的代理,一切顺利。
显然,这在每次调用期间都会在运行时产生成本。但是,与您正在制作任何 I/O 这一事实相比,这通常完全不重要,尤其是在您处理网络时。事实上,除非你处于一个紧密的循环中,否则即使不做也很不重要 I/O - 只有性能测试才能真正判断你是否接受成本。
如果你真的想预生成所有的方法体,而不是在运行时保持逻辑动态,你可以利用 LambdaExpression
给你的事实 CompileToMethod
。与 Compile
不同,您没有获得可以直接调用的漂亮的小委托,但它让您可以选择使用 lambda 表达式显式构建方法体——这反过来又允许您创建整个 class es 无需诉诸委托调用。
一个完整(但简单)的例子:
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 emits,这应该非常简单。我们定义一个动态程序集、模块、类型(理想情况下,您希望在单个动态程序集中一次定义所有类型)。棘手的部分是 Lambda.CompileToMethod
只支持静态方法,所以我们需要作弊。首先,我们创建一个以 this
作为参数的静态方法,并在那里编译 lamdba 表达式。然后,我们创建一个方法存根——一个简单的 IL,确保我们的静态方法被正确调用。最后,我们将接口方法绑定到存根上。
在我的例子中,我假设了一个无参数的方法,但是只要你确保 LambdaExpression
使用与接口方法完全相同的类型,存根就像做所有的一样简单Ldarg
一个序列,一个 Call
和一个 Ret
。如果你的真实代码(在静态方法中)足够短,它通常会被内联。由于 this
是一个和其他任何参数一样的参数,如果你喜欢冒险,你可以直接将生成的方法的方法体放入虚拟方法中 - 请注意你需要做不过,这是两次通过。
我目前实际上正在使用 CodeGeneration.Roslyn
包,它允许您集成到 MSBuild 管道并构建东西。例如,参见我的 REST swagger or solidity -> C# codegen.