William
William

Reputation: 8067

Setting an object's sub property using ILGenerator.Emit

I have some code that uses IlGenerator.Emit to create and populate a generic object using a datareader. It works great however I need to extend it to populate simple child objects when the database field name contains an underscore.

For example a database field named "Address_Line1" should populate the property Line1, which is a property of the Address property on the Entity. In C# code that basically...

Entity.Address.Line1 = "value from reader";

I tried writing c# code and used ILSpy to try identify the IL code I should be writing, but I keep getting memory errors etc.

The code below includes the current working IL code and I've included my code attempt with comments. Can anyone help me out?

public static DynamicBuilder<T> CreateBuilder(IDataRecord reader)
{
    var result = new DynamicBuilder<T>();
    var method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(IDataReader) }, typeof(T), true);

    var generator = method.GetILGenerator();

    generator.DeclareLocal(typeof(T));
    generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
    generator.Emit(OpCodes.Stloc_0);

    var getValue = reader.GetType().GetMethod("get_Item", new Type[] { typeof(int) });

    for (int i = 0; i < reader.FieldCount; i++)
    {
        var name = reader.GetName(i).Split('_'); // MY CODE
        var propertyInfo = typeof(T).GetProperty(name[0]);

        if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
        {
            var endIfLabel = generator.DefineLabel();

            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldc_I4, i);
            generator.Emit(OpCodes.Callvirt, typeof(IDataRecord).GetMethod("IsDBNull"));
            generator.Emit(OpCodes.Brtrue, endIfLabel);

            generator.Emit(OpCodes.Ldloc_0);
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Ldc_I4, i);
            generator.Emit(OpCodes.Callvirt, getValue);

            if (propertyInfo.PropertyType.Name.ToLower().Contains("nullable"))
                generator.Emit(OpCodes.Unbox_Any, GetNullableType(reader.GetFieldType(i)));
            else
                generator.Emit(OpCodes.Unbox_Any, reader.GetFieldType(i));

            // START MY CODE TO GET THE SUB PROPERTY
            if (name.Length > 1)
            {
                generator.Emit(OpCodes.Callvirt, propertyInfo.GetGetMethod());
                propertyInfo = propertyInfo.PropertyType.GetProperty(name[1]);
            }
            // END MY CODE

            generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());
            generator.MarkLabel(endIfLabel);
        }
    }

    generator.Emit(OpCodes.Ldloc_0);
    generator.Emit(OpCodes.Ret);

    result.handler = (Load)method.CreateDelegate(typeof(Load));
    return result;
}

Upvotes: 4

Views: 1353

Answers (2)

Hovhannes
Hovhannes

Reputation: 1

I'll suggest not use IL code/Emit at all since it is hard to build expressions with it. Instead try to use the new Roslyn to generate the delegates.

Here are some samples: https://github.com/dotnet/roslyn/wiki/Scripting-API-Samples

Upvotes: -2

svick
svick

Reputation: 244837

Code like this:

static Entity DynamicCreate(IDataReader reader)
{
    var entity = new Entity();
    entity.Property = (int)reader[0];
    return entity;
}

is compiled to IL that looks exactly like the code you're emitting (unimportant parts omitted):

ldloc.0     // entity
ldarg.0     // reader
ldc.i4.0    
callvirt    System.Data.IDataRecord.get_Item
unbox.any   System.Int32
callvirt    UserQuery+Entity.set_Property

But if you add that second property access:

static Entity DynamicCreate(IDataReader reader)
{
    var entity = new Entity();
    entity.SubEntity.Property = (int)reader[0];
    return entity;
}

Then the IL looks like this:

ldloc.0     // entity
callvirt    UserQuery+Entity.get_SubEntity
ldarg.0     // reader
ldc.i4.0    
callvirt    System.Data.IDataRecord.get_Item
unbox.any   System.Int32
callvirt    UserQuery+SubEntity.set_Property

Notice that the call to get_SubEntity is between ldloc.0 and ldarg.0, not right before set_Property like in your code, so you have to move it there in your code too.

The reason your code doesn't work is that IL is a stack based language: when you call a parameterless instance method (like a property getter), the object on the top of the stack (which here is the result of the unbox.any) will be used as its this, which is not what you want here. Basically, your code tries to do something like:

entity.Property = ((int)reader[0]).SubEntity;

Upvotes: 4

Related Questions