属性中字段上的ILGenerator调用方法

时间:2019-12-05 05:33:50

标签: c# .net-core-2.2 ilgenerator

我正在尝试在ILGenerator中编写一些有助于延迟加载的代码。我遇到麻烦的部分是在使用TypeBuilder构建的类的私有字段中找到的Load Method。

我要在IL中完成的任务如下

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;
        }
    }
}

以下是我到目前为止编写的ILGenerator代码

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);
}

1 个答案:

答案 0 :(得分:0)

作业有点棘手。

您必须将此指针放在最前面,即Ldarg_0,然后是要存储的值,然后是要存储的字段。

这是因为您必须将其想象为StoreField,例如= 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)

因此,您可能需要在注释行正下方添加另一个LdArg0。-->此

如果您想使代码更快,但可读性更差,则可以用“ Dup”替换第2行,后者应该快一些,并且只需复制栈顶,这意味着它将再次赋予相同的值