Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically create a class by interface

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.

like image 419
Alex Zhukovskiy Avatar asked Jul 13 '16 07:07

Alex Zhukovskiy


People also ask

Can we create dynamic class in C#?

You can create custom dynamic objects by using the classes in the System. Dynamic namespace. For example, you can create an ExpandoObject and specify the members of that object at run time. You can also create your own type that inherits the DynamicObject class.

How do you create an instance of a class dynamically in Java?

In order to dynamically create an object in Java from an inner class name, you should use the $ sign. For Example: String className = "MyTestClass"; String innerClassName = "MyInnerTestClass"; String fullPathOfTheClass = "full.

What is dynamic class in Java?

Dynamic Class Loading allows the loading of java code that is not known about before a program starts. Many classes rely on other classes and resources such as icons which make loading a single class unfeasible. For this reason the ClassLoader ( java. lang.


3 Answers

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 Ldargs 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.

like image 173
Luaan Avatar answered Oct 29 '22 14:10

Luaan


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).

like image 1
Slupka Avatar answered Oct 29 '22 14:10

Slupka


I'm actually currently using CodeGeneration.Roslyn package that allows you to integrate into MSBuild pipeline and build stuff. For example, see my REST swagger or solidity -> C# codegen.

like image 1
Alex Zhukovskiy Avatar answered Oct 29 '22 14:10

Alex Zhukovskiy