This is really fancy stuff, no one would probably never ever get along doing this. And I actually have no real straight forward “all day” use-case, I would ever recommend this for. But guess what, there is always an exception.
So why am I doing this?
My situation is the following: On one hand I have a database (actually a Realm.io database). I want to copy that Realm into another Realm, but I didn’t find a tool, doing that for me. Because that is an object oriented database, we need the model definitions right when opening the database, but I wanted to create a tool, to copy data from one realm to another, without actually knowing the class models at all. So there is a pretty neat construct called “DynamicRealm”, one can just open those by passing a parameter while opening the Realm called “IsDynamic”. Then I don’t need to have the actual classes in my code. When I then open an existing Realm, it shows me all information about the schema, that is stored in the database (properties, attributes, etc.)…
So far, so good, .. I can read data. But my destination Realm is absolutely blank, no schema, no data. And in .NET there is no method implemented to create my own schema. There is only one thing I can do – adding Types while opening the Realm, and Realm will take care of creating the schema. So I need to impement type-creation from the schema during runtime.
Creating the type during runtime
But before we create a type, we need to create an Assembly and Module, that type is going to be in. Be careful to use names, that aren’t already used. There are 3 Builders we use from the assembly System.Reflection.Emit : AssemblyBuilder, ModuleBuilder, TypeBuilder . In fact this part is pretty easy:
var parent = typeof(BaseClass); // the type of the baselcass for this type (can be null) var name = "MyCoolTypeName"; // name of the new type // 1. create assembly name var assemblyName = new AssemblyName($"SomeAssemblyName{name}"); // 2. create the assembly builder AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); // 3. that is needed to create a module builder ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule"); // 4. and finally our TypeBuilder (a public class) TypeBuilder tb = moduleBuilder.DefineType(name, TypeAttributes.Public | TypeAttributes.Class , parent); Type type = tb.CreateType(); // of type Type, we can even create instances of that typeBut when we want to create a real POCO we also need to add some properties to that type.
Add properties to the created Type (TypeBuilder)
This is not as easy, as it sounds like. We have to be aware, what is actually needed to create a property:
1. a backing field
2. a get method
3. a set method
Since we are in runtime mode, we need to create the method bodys in IL (which is the byte code used by .NET), so we cannot use plain C#.
(Be careful: all this can only run on systems with JIT compiler, so you cannot do that in UWP (with .Net Toolchain active) or iOS, Mono should be fine, but I didn’t test it yet)So how to find out what IL to use? Just create a TestClass and produce some IL (I prefer using dotPeek from JetBrains):
.method public hidebysig specialname instance string get_Text() cil managed { IL_0000: ldarg.0 // this IL_0001: ldfld string RealmCopy.TestClass::'_backingField' IL_0006: ret } // end of method TestClass::get_Text .method public hidebysig specialname instance void set_Text( string 'value' ) cil managed { IL_0000: ldarg.0 // this IL_0001: ldarg.1 // 'value' IL_0002: stfld string RealmCopy.TestClass::'_backingField' IL_0007: ret } // end of method TestClass::set_TextAfter that, we can just write down the code for adding a property to the TypeBuilder:
public static void CreateProperty(TypeBuilder tb, string propertyName, Type propertyType) { if (propertyType == null) // when the propertytype is null, we assume, it's the Type itself (so we set it to the TypeBuilder) { propertyType = tb; } if (propertyType == typeof(IList<>)) // same thing here for a IList with no generic type parameter -> we assume it's the type (we could probably do that for every generic type without type parameter) { propertyType = propertyType.MakeGenericType(tb); } FieldBuilder fieldBuilder = tb.DefineField("_" + propertyName, propertyType, FieldAttributes.Private); //creates the backing field PropertyBuilder propertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null); //get method MethodBuilder getPropMthBldr = tb.DefineMethod( "get_" + propertyName ,MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig , /*returnType*/ propertyType , /*parameter types*/ Type.EmptyTypes); // see IL for the right MethodAttributes ILGenerator getIl = getPropMthdBldr.GetILGenerator(); // create the code in the get method getIl.Emit(OpCodes.Ldarg_0); //this getIl.Emit(OpCodes.Ldfld, fieldBuilder); //backingfield getIl.Emit(OpCodes.Ret); // set method MethodBuilder setPropMthdBldr = tb.DefineMethod( "set_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig , /*returnType*/ null , /*parameter types*/ new[] { propertyType }); // see IL for the right MethodAttributes ILGenerator setIl = setPropMthdBldr.GetILGenerator(); // create the code in the set method setIl.Emit(OpCodes.Ldarg_0); //this setIl.Emit(OpCodes.Ldarg_1); // 'value' setIl.Emit(OpCodes.Stfld, fieldBuilder); // backingfield setIl.Emit(OpCodes.Nop); setIl.Emit(OpCodes.Ret); // add methods to the propertyBuilder propertyBuilder.SetGetMethod(getPropMthdBldr); propertyBuilder.SetSetMethod(setPropMthdBldr); }
1 Comment on “C# Creating Types during runtime (.Net Core)”