I have some expirience with .Net Expressions
, when I'm able to dynamically generate methods. It's fine, it's good.
But now I need to generate a whole class, and it seems that the only way to do it is Emit whole IL which is totally inacceptable (it's impossible to support).
Assume we have following interface:
public interface IFoo
{
[Description("5")]
int Bar();
[Description("true")]
bool Baz();
}
which should be converted to:
public class Foo : IFoo
{
public int Bar() => 5;
public bool Baz() => true;
}
How can I achieve it? Is it even possible without 3rd party tools and libs? I know there is plenty of useful utils on GitHub, but I really don't want to import a whole MVVM framework to just make some code generation.
If I could just use Expressions
, and create a class with methods I already generated with it. But for now I don't know how to do it.
First, since you're dealing with remoting, I have to mention that this is something that .NET was originally designed from the ground up to support (back from .NET's roots as COM 2.0). Your most straightforward solution would be to implement a transparent remoting proxy - just make your own (probably generic) class deriving from System.Runtime.Remoting.Proxies.RealProxy
, and you can provide all the logic necessary for implement whatever function you need by overriding the Invoke
method. Using GetTransparentProxy
, you get the proxy implementing your interface and you're good to go.
Obviously, this has a cost at runtime, during every invocation. However, it's usually entirely unimportant next to the fact that you're making any I/O at all, especially if you're dealing with the network. In fact, unless you're in a tight loop, it's quite unimportant even when not doing I/O - only performance testing can really tell if you you're fine with the cost or not.
If you really want to pregenerate all the method bodies, rather than keeping the logic dynamic at runtime, you can exploit the fact that LambdaExpression
gives you CompileToMethod
. Unlike Compile
, you don't get a nice little delegate you can call directly, but it gives you the option to use lambda expressions for building method bodies explicitly - which in turn allows you to make entire classes without resorting to delegate invocations.
A full (but simple) example:
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();
}
If you've ever worked with .NET emits, this should be pretty straightforward. We define a dynamic assembly, module, type (ideally, you'd want to define all your types at once, in a single dynamic assembly). The tricky part is that Lambda.CompileToMethod
only supports static methods, so we need to cheat a bit. First, we create a static method that takes this
as an argument and compile the lamdba expression there. Then, we create a method stub - a simple piece of IL that ensures our static method is called properly. Finally, we bind the interface method to the stub.
In my example, I assume a parameter-less method, but as long as you make sure that the LambdaExpression
uses exactly the same types as the interface method, the stub is as simple as doing all the Ldarg
s in a sequence, a single Call
and a single Ret
. And if your real code (in the static method) is short enough, it will often be inlined. And since this
is an argument like any other, if you're feeling adventurous, you could just take the method body of the generated method and put it directly into the virtual method - do note that you'd need to do that in two passes, though.
You can use CodeDOM and Emit. However, I don't think that it is worth it.
For a similar thing I am using the following library:
http://www.castleproject.org/projects/dynamicproxy/
It is able to create proxy classes even when the target class is not available (you have to intercept all methods then).