C# 反射从实现接口的泛型类型调用发出的方法时发出无效程序

问题描述 投票:0回答:1

我正在 C# 中动态生成一个代理来包装来自接口的派生类型,拦截来自接口的方法并使用拦截的参数调用另一个方法。

我的问题是,对于我生成的类型的某些方法,我收到无效的程序异常。但对于从接口实现的其他方法来说,它是有效的。我使用相同的代码来生成接口的方法实现:

var invokeMethod = typeof(ServiceInterceptor).GetMethod(nameof(Invoke), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException("Could not get current method");
var getCurrentMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetCurrentMethod), BindingFlags.Static | BindingFlags.Public) ?? throw new InvalidOperationException("Could not get current method");
var interfaceMethods = GetAllInterfaceMethods(interfaceType);
var objectGetTypeMethod = typeof(ServiceInterceptor).GetType().GetMethod(nameof(GetType), BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not get get type object method");
var objectTypeIsValueTypeGetMethod = typeof(ServiceInterceptor).GetType().GetProperty(nameof(Type.IsValueType), BindingFlags.Public | BindingFlags.Instance)?.GetMethod ?? throw new InvalidOperationException("Could not get the method is value type");

foreach (var interfaceMethod in interfaceMethods)
{
    var interfaceMethodTypeBuilder = serviceInterceptorTypeBuilder.DefineMethod(interfaceMethod.Name, interfaceMethod.Attributes & ~MethodAttributes.Abstract, interfaceMethod.CallingConvention);
    builtMethods.Add(interfaceMethodTypeBuilder);
    var interfaceMethodArgumentsType = new List<Type>();
    var interfaceMethodGenericArguments = interfaceMethod.GetGenericArguments();
    var interfaceMethodGenericParametersMap = new Dictionary<Type, GeneraticParameterTypeWithInitialization>();
    if (serviceInterceptorGenericParametersMap != null && serviceInterceptorGenericParametersMap.Any())
    {
        foreach (var parameterMap in serviceInterceptorGenericParametersMap)
        {
            interfaceMethodGenericParametersMap[parameterMap.Key] = parameterMap.Value;
        }
    }
    if (interfaceMethodGenericArguments.Any())
    {
        var genericMethodParameters = interfaceMethodTypeBuilder.DefineGenericParameters(interfaceMethodGenericArguments.Select(arg => arg.Name).ToArray());
        for (int i = 0; i < genericMethodParameters.Length; i++)
        {
            interfaceMethodGenericParametersMap[interfaceMethodGenericArguments[i]] = new GeneraticParameterTypeWithInitialization { GenericTypeParameterBuilder = genericMethodParameters[i] };
        }
        foreach (var interfaceMethodGenericArgument in interfaceMethodGenericArguments)
        {
            foreach (var contraint in interfaceMethodGenericArgument.GetGenericParameterConstraints())
            {
                var replacedConstraint = ReplaceGenericArgumentsAndConstraintsFromType(contraint, interfaceMethodGenericParametersMap);
                if (replacedConstraint.IsInterface)
                {
                    interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetInterfaceConstraints(replacedConstraint);
                    continue;
                }
                interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetBaseTypeConstraint(replacedConstraint);
                interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetGenericParameterAttributes(contraint.GenericParameterAttributes & ~GenericParameterAttributes.Covariant & ~GenericParameterAttributes.Contravariant);
            }
        }
    }
    var interfaceParameters = interfaceMethod.GetParameters();
    var interfaceMethodParameters = interfaceParameters.Select(x => ReplaceGenericArgumentsFromType(x.ParameterType, serviceInterceptorGenericParametersMap)).ToArray();
    interfaceMethodTypeBuilder.SetParameters(interfaceMethodParameters);
    interfaceMethodTypeBuilder.SetReturnType(ReplaceGenericArgumentsFromType(interfaceMethod.ReturnType, serviceInterceptorGenericParametersMap));

    var ilGenerator = interfaceMethodTypeBuilder.GetILGenerator();
    var localOjectParamList = ilGenerator.DeclareLocal(typeof(object[]));
    var localObjectParam = ilGenerator.DeclareLocal(typeof(object));
    var localMethodInfo = ilGenerator.DeclareLocal(typeof(MethodInfo));
    ilGenerator.EmitWriteLine("Calling generated interface method");
    ilGenerator.Emit(OpCodes.Call, getCurrentMethod);
    ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo));
    ilGenerator.Emit(OpCodes.Stloc, localMethodInfo);
    if (!interfaceMethod.GetParameters().Any())
    {
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Ldloc, localMethodInfo);
        ilGenerator.Emit(OpCodes.Ldnull);
        ilGenerator.Emit(OpCodes.Callvirt, invokeMethod);
        ilGenerator.Emit(OpCodes.Ret);
        continue;
    }

    ilGenerator.Emit(OpCodes.Ldc_I4, interfaceParameters.Length);
    ilGenerator.Emit(OpCodes.Newarr, typeof(object));
    ilGenerator.Emit(OpCodes.Stloc, localOjectParamList);

    for (short parameterIndex = 0; parameterIndex < interfaceParameters.Length; parameterIndex++)
    {
        var loadArgumentLabel = ilGenerator.DefineLabel();
        var loadNextArgumentLabel = ilGenerator.DefineLabel();
        
        ilGenerator.Emit(OpCodes.Ldtoken, interfaceMethodParameters[parameterIndex]);
        ilGenerator.Emit(OpCodes.Call, getTypeFromHandle);
        ilGenerator.Emit(OpCodes.Callvirt, objectTypeIsValueTypeGetMethod);
        ilGenerator.Emit(OpCodes.Brfalse, loadArgumentLabel);
        ilGenerator.EmitWriteLine("Detected value type, boxing it");
        ilGenerator.Emit(OpCodes.Ldarg, (short)(parameterIndex + 1));
        ilGenerator.Emit(OpCodes.Box, interfaceMethodParameters[parameterIndex]);
        ilGenerator.Emit(OpCodes.Stloc, localObjectParam);
        ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
        ilGenerator.Emit(OpCodes.Ldc_I4, (int)parameterIndex);
        ilGenerator.Emit(OpCodes.Ldloc, localObjectParam);
        ilGenerator.Emit(OpCodes.Stelem_Ref);
        ilGenerator.Emit(OpCodes.Br, loadNextArgumentLabel);
        ilGenerator.MarkLabel(loadArgumentLabel);
        ilGenerator.EmitWriteLine("Detected reference type,putting it directly in array");
        ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
        ilGenerator.Emit(OpCodes.Ldc_I4, (int)parameterIndex);
        ilGenerator.Emit(OpCodes.Ldarg, (short)(parameterIndex + 1));
        ilGenerator.Emit(OpCodes.Stelem_Ref);

        ilGenerator.MarkLabel(loadNextArgumentLabel);
    }

    ilGenerator.Emit(OpCodes.Ldarg_0);
    ilGenerator.Emit(OpCodes.Ldloc, localMethodInfo);
    ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
    ilGenerator.Emit(OpCodes.Callvirt, invokeMethod);
    ilGenerator.Emit(OpCodes.Ret);
}

我还确保将接口方法上的类型替换为新的动态创建类型上的泛型参数类型。

我不知道为什么从接口生成方法实现的代码适用于返回某些内容并具有简单参数的方法,而适用于不返回任何内容且具有泛型类型参数的方法。不工作。生成的代码几乎相同。也许我需要对返回 void 的方法做一些特殊的事情?

c# reflection system.reflection reflection.emit typebuilder
1个回答
0
投票

确保对于返回 void 的方法,在 Callvirt 指令之后立即发出 Ret 指令(指示方法的结束)。 我希望这能解决您的问题。

© www.soinside.com 2019 - 2024. All rights reserved.