views:

34

answers:

2

I need to create a type at runtime using the TypeBuilder. This type should implement a specific interface so that it is possible to treat instances of this dynamic type uniformly at the compile time.

The interface should return an array of objects filled with values of specific fields in that type.

Interface that is supposed to be implemented is defined as follows:

public interface ISelectable
{
    object[] GetPrimaryKeysValues();
}

This is the code I use to generate a method for the interface:

public static Type BuildTypeFromTable(Table tableToBuildTypeFrom)
{
    AssemblyBuilder customTypesAssembly =
            AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName("CustomTypesAssembly"), AssemblyBuilderAccess.Run);

    ModuleBuilder _moduleBuilder = customTypesAssembly.DefineDynamicModule("CustomTypesModule");

    TypeBuilder customTypeBuilder = _moduleBuilder.DefineType(Guid.NewGuid().ToString(), TypeAttributes.Public | TypeAttributes.Class);

    List<FieldBuilder> primaryKeyFields = new List<FieldBuilder>();

    //create a property for each column in the table
    for (int i = 0; i < tableToBuildTypeFrom.Columns.Count; i++)
    {
        string propertyName = tableToBuildTypeFrom.Columns[i].Name;
        //get a type of a property to create from a first row of the table
        Type propertyType = tableToBuildTypeFrom.GetTypeOfColumnAtIndex(i);

        //each property has to have a field to store its value in
        FieldBuilder backingField = customTypeBuilder.DefineField(propertyName + "_field", propertyType, FieldAttributes.Private);

        //body of a property getter
        MethodBuilder getMethod = customTypeBuilder.DefineMethod(propertyName + "_get", MethodAttributes.Public | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
        ILGenerator getIlGenerator = getMethod.GetILGenerator();
        getIlGenerator.Emit(OpCodes.Ldarg_0);
        getIlGenerator.Emit(OpCodes.Ldfld, backingField);
        getIlGenerator.Emit(OpCodes.Ret);

        ///body of a property setter
        MethodBuilder setMethod = customTypeBuilder.DefineMethod(propertyName + "_set", MethodAttributes.Public | MethodAttributes.HideBySig, null, new Type[] { propertyType });
        ILGenerator setIlGenerator = setMethod.GetILGenerator();
        setIlGenerator.Emit(OpCodes.Ldarg_0);
        setIlGenerator.Emit(OpCodes.Ldarg_1);
        setIlGenerator.Emit(OpCodes.Stfld, backingField);
        setIlGenerator.Emit(OpCodes.Ret);

        PropertyBuilder customProperty = customTypeBuilder.DefineProperty(propertyName, PropertyAttributes.None, propertyType, Type.EmptyTypes);
        customProperty.SetGetMethod(getMethod);
        customProperty.SetSetMethod(setMethod);

        //save all primary key columns to avoid iterating over columns all over again
        if (tableToBuildTypeFrom.Columns[i].IsPrimaryKey)
        {
            primaryKeyFields.Add(backingField);
        }
    }

    customTypeBuilder.AddInterfaceImplementation(typeof(ISelectable));

    MethodBuilder getPrimaryKeysMethod = customTypeBuilder.DefineMethod("GetPrimaryKeysValues", MethodAttributes.Public | MethodAttributes.Virtual, typeof(object[]), null);
    ILGenerator getPrimaryKeysMethodIlGenerator = getPrimaryKeysMethod.GetILGenerator();

    getPrimaryKeysMethodIlGenerator.DeclareLocal(typeof(object[]));
    getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Ldc_I4, primaryKeyFields.Count);
    getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Newarr, typeof(object));
    getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Stloc_0);

    for (int i = 0; i < primaryKeyFields.Count; i++)
    {
        getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Ldloc_0);
        getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Ldc_I4, i);

        getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Ldarg_0);
        getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Ldfld, primaryKeyFields[i]);

        getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Stelem_Ref);
    }

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

    MethodInfo s = typeof(ISelectable).GetMethod("GetPrimaryKeysValues");
    customTypeBuilder.DefineMethodOverride(getPrimaryKeysMethod, s);

    return customTypeBuilder.CreateType();
}

The way the method is supposed to be generated is taken from MSDN.

Now, the problem is that each time I try to call GetPrimaryKeysValues method, the VerificationException with a message 'Operation could destabilize the runtime.' is thrown. I have no idea what is causing it. Could someone help?

Thank you!

+1  A: 

You didn't declare local variable, but you use it (Stloc_0,Ldloc_0).

Insert this right before generating IL code:

getPrimaryKeysMethodIlGenerator.DeclareLocal(typeof(object[]));
Andrey
I've added your line of code, but it still does not work. The same exception keeps getting thrown.I've updated my initial post to reflect your suggestion, though, it certainly is a correct one. Thank you!
Lotar
@Lotar put the whole code please. because ifter i added local variable to that piece you gave it started working.
Andrey
@Lotar i have a feeling that your stack is imbalanced, you have more pushes (ldXXX) then pops (other)
Andrey
I've edited my original post and put a whole method that generates a type in there.
Lotar
+2  A: 

The stelem.ref opcode stores an object reference into an array. This means that if you have fields of value types (e.g. ints), then you need to make sure to box them before storing them into the array. Therefore, you should add something like

if (primaryKeyFields[i].FieldType.IsValueType) {
    getPrimaryKeysMethodIlGenerator.Emit(OpCodes.Box, primaryKeyFields[i].FieldType);
}

immediately before the stelem.ref instruction.

Also, although this should not cause the verification exception, note that your properties' method names should be get_{Name} and set_{Name}, not {Name}_get and {Name}_set.

kvb
Thank you, that did the trick!I just had to change your code a bit - to make it work, a Type parameter for the Box opcode should be typeof(primaryKeyFields[i].FieldType), not typeof(object). Otherwise, I got the FatalExecutionEngineError.I've also changed my properties method names as you suggested. Is it some kind of a naming convention?
Lotar
@Lotar - Yes, that's the naming convention used by the Common Language Specification (see the "Accessor names" section of http://msdn.microsoft.com/en-us/library/12a7a7h3.aspx). I've edited my answer to fix the type used with the box instruction.
kvb