Monthly Archives: October 2019

read and write properties on dynamic objects in .NET/C#

Ok, the headline sounds a little bit funny, because first of all, dynamic objects have no properties. Also you can not use reflection on a dynamic type, because it’s dynamic. What I actually want to do, is to access values of a dynamic object by its name – but I don’t know the name of the property on compile time, but during execution.

I want to do something like that:

dynamic obj = GetDynamicObject();
string myPropertyValue = DynamicExtension.GetValue(obj, "MyProperty");
DynamicExtension.SetValue(obj, "MyProperty", "some cool value");

So how can we do this? Reflection is not possible, and not all dynamic types impement IDictionary like ExpandoObject.

But how can we achieve this? By looking at the disassembled code, of a dynamic class, and the way it is accessed, we can find out, how we can access it ourself…

dynamic dynObj = new TestClass
{
  TestProperty = "Hello World",
  TestInt = 20
};
var val = dynObj.TestProperty;
dynObj.TestProperty = "Hello C#";

Because I don’t want to flood this post, I just copied the essentials from the IL. Basically there are 2 parts:
1. create a Binder
2. create a CallSite to execute the getter/setter.

// the getter
            var binder = Binder.GetMember(
                CSharpBinderFlags.None,
                "TestProperty",
                typeof(object),
                new CSharpArgumentInfo[] { 

 CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.NamedArgument, null)
                });

            var getter = CallSite<Func<CallSite,object,object>>.Create(binder);
            var val = getter.Target(getter,  dynObj);

// the setter
var setterBinder = Binder.SetMember(
    CSharpBinderFlags.None,
    "TestProperty",
    typeof(object),
    new CSharpArgumentInfo[] {
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) });
// the IL actually uses string as value type, but we use object here, to be more flexible
var setter = CallSite<Func<CallSite,object,object,object>>.Create(setterBinder);
setter.Target(setter, dynObj, "Hello C#");

So now just wrap it up to our two functions:

    public static class DynamicHelper
    {
        public static object GetValue(object obj, string name)
        {
            var binder = Binder.GetMember(
                        CSharpBinderFlags.None,
                        name,
                        typeof(object),
                        new CSharpArgumentInfo[] {
                            CSharpArgumentInfo.Create(
                                CSharpArgumentInfoFlags.NamedArgument,
                                null)
                        });

            var getter = CallSite<Func<CallSite,object,object>>.Create(binder);
            return getter.Target(getter, obj);
        }

        public static void SetValue(object obj, string name, object value)
        {
            var setterBinder = Binder.SetMember(
                CSharpBinderFlags.None,
                name,
                typeof(object),
                new CSharpArgumentInfo[] {
                  CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                  CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.UseCompileTimeType, null) });
            // the IL actually uses string as value type, but we use object here, to be more dynamic
            var setter = CallSite<Func<CallSite,object,object,object>>.Create(setterBinder);
            setter.Target(setter, obj, value);
        }
    }

C# Creating Types during runtime (.Net Core)

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

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

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&amp;amp;lt;&amp;amp;gt;)) // same thing here for a IList with no generic type parameter -&amp;amp;gt; 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);
}