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:
123456789101112131415var
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 type
But 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):
1234567891011121314151617181920.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_Text
After that, we can just write down the code for adding a property to the TypeBuilder:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152public
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)”