Search code examples
c#.net-core-2.2ilgenerator

ILGenerator call method on field from property


I am trying write some code in ILGenerator that would facilitate lazy loading. the part I am having trouble with is the Load Method found on a private field in the class being built using TypeBuilder.

what I am trying to accomplish in IL is the following

class Proxy
    : BaseType
{
    private DbContextAccessor _accessor;

    private Status _Status;

    public override Status Status
    {
        get
        {
            if (_Status.IsNull())
            {
                PropertyInfo info = GetType().GetProperty("Status");

                _Status = _accessor.Load<BaseType, Status>(this, info);
            }
            return _Status;
        }
        set
        {
            _Status = value;
        }
    }
}

the following is the ILGenerator code that I have written so far

private void BuildOverriddenProperty(string propertyName, Type propertyType)
{
    FieldBuilder propertyField = typeBuilder.DefineField($"_{propertyName}", propertyType, FieldAttributes.Private);

    PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(
            propertyName,
            PropertyAttributes.None,
            propertyType,
            Type.EmptyTypes);

    MethodAttributes methodAttributesForGetAndSet = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.NewSlot | MethodAttributes.Virtual ;

    MethodBuilder
        getMethod = typeBuilder.DefineMethod($"get_{propertyName}", methodAttributesForGetAndSet, propertyType, Type.EmptyTypes),
        setMethod = typeBuilder.DefineMethod($"set_{propertyName}", methodAttributesForGetAndSet, null, new Type[] { propertyType });

    ILGenerator
        iLGetGenerator = getMethod.GetILGenerator(),
        iLSetGenerator = setMethod.GetILGenerator();

    Type internalExt = typeof(InternalExtensions);

    MethodInfo
        load = fieldDbContextAccessor.FieldType.GetMethods()
            .Where(method =>
                method.Name == (propertyType.IsClass ? "Load" : "LoadCollection"))
            .Single().MakeGenericMethod(baseType),
        isNull = internalExt.GetMethod("IsNull", BindingFlags.Public | BindingFlags.Static , null, new[] { typeof(object) }, null);

    Label fieldIsNotNull = iLGetGenerator.DefineLabel();

    LocalBuilder
        propertyInfo = iLGetGenerator.DeclareLocal(typeof(PropertyInfo));

    iLGetGenerator.Emit(OpCodes.Ldarg_0);                   // this
    iLGetGenerator.Emit(OpCodes.Ldfld, propertyField);      // propertyField
    iLGetGenerator.EmitCall(OpCodes.Call, isNull, null);    // use the static extension method IsNull
    iLGetGenerator.Emit(OpCodes.Brfalse_S, fieldIsNotNull); // value is not null
    //{
        iLGetGenerator.Emit(OpCodes.Ldarg_0);                                                                               // this
        iLGetGenerator.EmitCall(OpCodes.Call, typeof(object).GetMethod("GetType"), null);                                   // call GetType method
        iLGetGenerator.Emit(OpCodes.Ldstr, propertyName);                                                                   // push new string of propertyName
        iLGetGenerator.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetProperty", new[] { typeof(string) }), null);       // call GetProperty with the propertyName as the parameter
        iLGetGenerator.Emit(OpCodes.Stloc, propertyInfo);                                                                   // store PropertyInfo object in the local variable propertyInfo

        // -> this is the problem area that results in invalid program code
        iLGetGenerator.Emit(OpCodes.Ldarg_0);                           // this
        iLGetGenerator.Emit(OpCodes.Ldfld, fieldDbContextAccessor);     // field variable _accessor
        iLGetGenerator.Emit(OpCodes.Ldarg_0);                           // this ptr as the first parameter // <- my hunch is this is the problem but uncertain
        iLGetGenerator.Emit(OpCodes.Ldloc, propertyInfo);               // local variable propertyInfo as the second parameter
        iLGetGenerator.EmitCall(OpCodes.Call, load, null);              // call the Load or LoadCollection on the DBContextAccessor object
        iLGetGenerator.Emit(OpCodes.Stfld, propertyField);              // store the return in the propertyField
        // -> end
    //}
    iLGetGenerator.MarkLabel(fieldIsNotNull);               // jump here when propertyField is not null
    iLGetGenerator.Emit(OpCodes.Ldarg_0);   // this
    iLGetGenerator.Emit(OpCodes.Ldfld, propertyField); // propertyField
    iLGetGenerator.Emit(OpCodes.Ret);

    typeBuilder.DefineMethodOverride(getMethod, baseType.GetProperty(propertyName).GetMethod);

    iLSetGenerator.Emit(OpCodes.Ldarg_0);
    iLSetGenerator.Emit(OpCodes.Ldarg_1);
    iLSetGenerator.Emit(OpCodes.Stfld, propertyField);
    iLSetGenerator.Emit(OpCodes.Ret);

    typeBuilder.DefineMethodOverride(setMethod, baseType.GetProperty(propertyName).SetMethod);
}

Solution

  • An assignment is a little tricky.

    You have to put this this pointer at very first place, the Ldarg_0, then comes the value you want to store and then the field you want to store to.

    This is cause you have to imagine it as StoreField like = Assign(this, value).

    1:LdArg0, So you put the this from PropertyField on the stack
    2:LdArg0, you put the ThisPointer from _accessor on the stack
    3:LdFld accessor, you load the accessor, remove one value from stack  from line (2)
    4:LdArg0, you load the this pointer, this time your explicit parameter
    5:LdLoc propertyInfo, you load your third parameter
    6:Call load, // this will take 3 values from stack, lines (3)-(5)
    7;Stfld propertyfield, // you store the property, on stack is the this pointer line(1) and the output of load line(6)
    

    So you might need to Add another LdArg0, directly below your comment line.-->this

    If you want to make your code faster, but less readable, you can replace line2, with "Dup", that supposed to be little faster, and just duplicates the top of the stack, meaning it puts the same value once again.