mardi 17 décembre 2019

How to define and use a lambda with an ILGenerator

Given these types (unknown at compile time)

public class Type0
{
    public List<Type1> EnumerableProperty { get; set; }
}

public class Type1 { }

I'm trying to generate the following types using IL

public class GeneratedType0
{
  private Type0 _content;
  public GeneratedType0(Type0 arg)
  {
    _content = arg;
  }

  public List<Type1> EnumerableProperty => _content.EnumerableProperty.Select(x => new GeneratedType1(x));
}

public class GeneratedType1
{
  private Type1 _content;
  public GeneratedType1(Type1 arg)
  {
    _content = arg;
  }
}

I have everything down, except for the use of a lambda inside the Select method. The following code is my best attempt to add a IEnumerable property to a generated type.

Does anybody know what's wrong with my IL code supposed to add the property to the generated type?

        public static readonly MethodAttributes _getAttributes = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;
        public static readonly MethodAttributes _getLambdaAttributes = MethodAttributes.Public | MethodAttributes.Static;

        public static void AddEnumerableProperty(PropertyInfo property, Type initialPropertyElementType, Type propertyType, Type elementType, TypeBuilder builder, FieldBuilder fieldBuilder)
        {
            var propertyBuilder = builder.DefineProperty(property.Name, PropertyAttributes.None, propertyType, null); // EnumerableProperty on GeneratedType0 

            var getterBuilder = builder.DefineMethod($"get_{property.Name}", _getAttributes, propertyType, Type.EmptyTypes);
            var propertyGetterIL = getterBuilder.GetILGenerator();
            propertyGetterIL.Emit(OpCodes.Ldarg_0); //this
            propertyGetterIL.Emit(OpCodes.Ldfld, fieldBuilder); //.content
            propertyGetterIL.Emit(OpCodes.Callvirt, property.GetGetMethod() ?? throw new ArgumentException($"Property {property.Name} is not readable on type {property.DeclaringType}.")); //.property

            var lambda = builder.DefineMethod("To" + elementType.Name, _getLambdaAttributes, CallingConventions.Standard, elementType, new[] { initialPropertyElementType });
            var lambdaGenerator = lambda.GetILGenerator();
            lambdaGenerator.Emit(OpCodes.Ldarg_0); //arg
            lambdaGenerator.Emit(OpCodes.Newobj, elementType.GetConstructors()[0]);//new(...)
            lambdaGenerator.Emit(OpCodes.Ret); //return

            propertyGetterIL.Emit(OpCodes.Ldobj, lambda;// //ToElementType

            var method = SelectMethod.MakeGenericMethod(initialPropertyElementType, elementType);
            propertyGetterIL.Emit(OpCodes.Call, method);//Select<TInput, TResult>(
            propertyGetterIL.Emit(OpCodes.Ret); //return
            propertyBuilder.SetGetMethod(getterBuilder);
        }




Aucun commentaire:

Enregistrer un commentaire