Create Dynamic Type in C# at run time

896 views Asked by At

I am trying to create a c# class dynamically at run time.

using System;

class Hist
{
  private int? _min;
  private  int? _max;

  public int? min
  {
      get{return _min;}
      set {_min = value;}
  }

  public int? max
  {
      get{return _max;}
      set {_max = value;}
  }
}

public class ProcessData
{
  private string _id;
  private string _source;
  private int? _currentValue;
  private Hist _hist;

  public Hist hist
  {
      get { return _hist; }
      set{ _hist = value; }
  }

  public string id 
  {
      get {return _id;}
      set { _id = value; }
  }

  public string source 
  {
      get {return _source;}
      set { _source = value; }
  }

  public int? currentValue 
  {
       get {return _currentValue;}
       set { _currentValue = value; }
  }

  public int? min
  {
      get { return (hist != null) ? hist.min : null; }        
  }
  public int? max
  {
      get { return (hist != null) ? hist.max : null; }        
  }
}

But i am unable to do this specifically.

return (hist != null) ? hist.max : null;

i just need is the get method for any of the min or max property of ProcessData class.

My code for above task:

var method = parentType.GetMethod("get_" + propertyName);
getPropMthdBldr = tb.DefineMethod("get_" + propertyName, 
      MethodAttributes.Public | 
      MethodAttributes.SpecialName | 
      MethodAttributes.HideBySig, 
    propertyType, Type.EmptyTypes);
getIl = getPropMthdBldr.GetILGenerator();
var moveTo = getIl.DefineLabel();
getIl.Emit(OpCodes.Ldarg_0);
getIl.EmitCall(OpCodes.Call, parentGetMethod, Type.EmptyTypes);
getIl.Emit(OpCodes.Brtrue_S, moveTo);
getIl.Emit(OpCodes.Ldloca_S, 0);
getIl.Emit(OpCodes.Initobj, typeof(int?));
getIl.Emit(OpCodes.Ldloc_0);
getIl.Emit(OpCodes.Ret);
getIl.MarkLabel(moveTo);
getIl.Emit(OpCodes.Ldarg_0);
getIl.EmitCall(OpCodes.Call, parentGetMethod,Type.EmptyTypes);
getIl.EmitCall(OpCodes.Callvirt, method,Type.EmptyTypes);
getIl.Emit(OpCodes.Ret); 
1

There are 1 answers

0
Andrey Tretyak On

Problem is that you are trying to use local variable that not declared:

getIl.Emit(OpCodes.Ldloca_S, 0);           // load address of local variable with index 0 on stack 
getIl.Emit(OpCodes.Initobj, typeof(int?)); // initialize local variable
getIl.Emit(OpCodes.Ldloc_0);               // load value of local variable with index 0 on stack

You can define required local variable like this:

var local = getIl.DeclareLocal(typeof(int?));

And your code will be valid, but to improve readability I would advise you to use variable local instead index. It could be done like this:

getIl.Emit(OpCodes.Ldloca_S, local);       // load address of local variable on stack
getIl.Emit(OpCodes.Initobj, typeof(int?)); // initialize local variable
getIl.Emit(OpCodes.Ldloc, local);          // load value of local variable on stack

P.S. I'll put here code that I've used for generation of classes Hist and ProcessData, may be it could be useful for you in some way, in case if my explanation was not sufficient.

Main logic in this helper for property creation:

public static class TypeBuilderExtensions
{
    public static PropertyBuilder CreateProperty<T>(this TypeBuilder builder, string name) => CreateProperty(builder, typeof(T), name);

    public static PropertyBuilder CreateProperty(this TypeBuilder builder, Type propertyType,  string name)
    {
        var field = builder.DefineField($"_{name}", propertyType, FieldAttributes.Private);
        var getMethodBuilder = builder.DefineMethod($"get_{name}", MethodAttributes.Public, propertyType, Type.EmptyTypes);
        var getGenerator = getMethodBuilder.GetILGenerator();
        getGenerator.Emit(OpCodes.Ldarg_0);
        getGenerator.Emit(OpCodes.Ldfld, field);
        getGenerator.Emit(OpCodes.Ret);

        var setMethodBuilder = builder.DefineMethod($"set_{name}", MethodAttributes.Public, typeof(void), new[] { propertyType });
        var setGenerator = setMethodBuilder.GetILGenerator();
        setGenerator.Emit(OpCodes.Ldarg_0);
        setGenerator.Emit(OpCodes.Ldarg_1);
        setGenerator.Emit(OpCodes.Stfld, field);
        setGenerator.Emit(OpCodes.Ret);

        var propertyBuilder = builder.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);
        propertyBuilder.SetGetMethod(getMethodBuilder);
        propertyBuilder.SetSetMethod(setMethodBuilder);
        return propertyBuilder;
    }

    public static PropertyBuilder CreateCalculatedProperty<T>(this TypeBuilder builder, string name, MethodInfo getObject, MethodInfo getObjectProperty) => CreateCalculatedProperty(builder, typeof(T), name, getObject, getObjectProperty);

    public static PropertyBuilder CreateCalculatedProperty(this TypeBuilder builder, Type propertyType, string name, MethodInfo getObject, MethodInfo getObjectProperty)
    {
        var getMethodBuilder = builder.DefineMethod($"get_{name}", MethodAttributes.Public, propertyType, Type.EmptyTypes);
        var getGenerator = getMethodBuilder.GetILGenerator();
        var label = getGenerator.DefineLabel();
        var local = getGenerator.DeclareLocal(propertyType);
        getGenerator.Emit(OpCodes.Ldarg_0);
        getGenerator.Emit(OpCodes.Callvirt, getObject);
        getGenerator.Emit(OpCodes.Brtrue, label);
        getGenerator.Emit(OpCodes.Ldloca_S, local);
        getGenerator.Emit(OpCodes.Initobj, propertyType);
        getGenerator.Emit(OpCodes.Ldloc, local);
        getGenerator.Emit(OpCodes.Ret);
        getGenerator.MarkLabel(label);
        getGenerator.Emit(OpCodes.Ldarg_0);
        getGenerator.Emit(OpCodes.Callvirt, getObject);
        getGenerator.Emit(OpCodes.Callvirt, getObjectProperty);
        getGenerator.Emit(OpCodes.Ret);

        var propertyBuilder = builder.DefineProperty(name, PropertyAttributes.None, propertyType, Type.EmptyTypes);
        propertyBuilder.SetGetMethod(getMethodBuilder);
        return propertyBuilder;
    }
}

Here is class creation itself:

var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("TestAssembly"), AssemblyBuilderAccess.Run);
var moduleBuilder = assemblyBuilder.DefineDynamicModule("TestModule");
var histBuilder = moduleBuilder.DefineType("Hist");
var minProperty = histBuilder.CreateProperty<int?>("min");
var maxProperty = histBuilder.CreateProperty<int?>("max");

var processDataBuilder = moduleBuilder.DefineType("ProcessData");
var histProperty = processDataBuilder.CreateProperty(histBuilder, "hist");
processDataBuilder.CreateProperty<string>("id");
processDataBuilder.CreateProperty<string>("source");
processDataBuilder.CreateProperty<int?>("currentValue");

processDataBuilder.CreateCalculatedProperty<int?>("min", histProperty.GetMethod, minProperty.GetMethod);
processDataBuilder.CreateCalculatedProperty<int?>("max", histProperty.GetMethod, maxProperty.GetMethod);

And finally primitive validation of created classes:

void ValidateProperty(object instance, string name, object value, bool setValue = true)
{
    var type = instance.GetType();
    var property = type.GetProperty(name);
    if (setValue) property.SetValue(instance, value);
    var result = property.GetValue(instance);

    var equals = property.PropertyType.IsValueType && value != null ? value.Equals(result) : value == result;
    if (!equals)
        throw new InvalidDataException("Property not valid");
}

var histType = histBuilder.CreateType();
var histInstance = Activator.CreateInstance(histType);
ValidateProperty(histInstance, "min", 12);
ValidateProperty(histInstance, "max", 21);

var processDataType = processDataBuilder.CreateType();
var processDataInstance = Activator.CreateInstance(processDataType);
ValidateProperty(processDataInstance, "hist", histInstance);
ValidateProperty(processDataInstance, "id", "Test!");
ValidateProperty(processDataInstance, "source", "Source#");
ValidateProperty(processDataInstance, "currentValue", 126);

ValidateProperty(processDataInstance, "min", 12, false);
ValidateProperty(processDataInstance, "max", 21, false);

ValidateProperty(processDataInstance, "hist", null);
ValidateProperty(processDataInstance, "min", null, false);
ValidateProperty(processDataInstance, "max", null, false);