繁体   English   中英

在调用的方法中抛出异常时,如何使用MethodInfo.Invoke获取作为引用传递的参数值

[英]How to get a parameter value passed as a reference with MethodInfo.Invoke when throws an exception in the invoked method

我想知道调用方法的out / ref参数的值是多少。

在不抛出异常的情况下调用该方法时,会在参数中接收该值,但是在调用的方法中抛出异常时,我不会获取该值。 直接调用没有Reflection的方法,接收值。

我做错了什么或这是一个.net限制?

using System;
using System.Reflection;

class Program
{
    static void Main()
    {
        string[] arguments = new string[] { bool.FalseString, null }; 
        MethodInfo method = typeof(Program).GetMethod("SampleMethod");
        try
        {
            method.Invoke(null, arguments);
            Console.WriteLine(arguments[1]); // arguments[1] = "Hello", Prints Hello
            arguments = new string[] { bool.TrueString, null };
            method.Invoke(null, arguments);
        }
        catch (Exception)
        {
            Console.WriteLine(arguments[1]); // arguments[1] = null, Does not print
        }
        arguments[1] = null;
        try
        {
            SampleMethod(bool.TrueString, out arguments[1]);
        }
        catch (Exception)
        {
            Console.WriteLine(arguments[1]); // arguments[1] = "Hello"
        }
    }

    public static void SampleMethod(string throwsException, out string text)
    {
        text = "Hello";
        if (throwsException == bool.TrueString)
            throw new Exception("Test Exception");
    }
}

搜索了一下后,我找到了下面的解决方案。 会好用的吗?

using System;
using System.Reflection;
using System.Reflection.Emit;

public static class MethodInfoExtension
{
    public static object InvokeStrictly(this MethodInfo source, object obj, object[] parameters)
    {
        ParameterInfo[] paramInfos = source.GetParameters();
        if ((parameters == null) || (paramInfos.Length != parameters.Length))
        {
            throw new ArgumentException();
        }

        Type[] paramTypes = new[] { typeof(object[]) };
        DynamicMethod invokerBuilder = new DynamicMethod(string.Empty, typeof(object), paramTypes);

        ILGenerator ilGenerator = invokerBuilder.GetILGenerator();
        Label exBlockLabel = ilGenerator.BeginExceptionBlock();

        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;
            var paramType = paramIsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType;

            ilGenerator.DeclareLocal(paramType);

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            Label label1 = ilGenerator.DefineLabel();
            ilGenerator.Emit(OpCodes.Brfalse, label1);

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            ilGenerator.Emit(OpCodes.Unbox_Any, paramType);
            ilGenerator.Emit(OpCodes.Stloc_S, (byte)i);

            ilGenerator.MarkLabel(label1);

            if (paramIsByRef)
            {
                ilGenerator.Emit(OpCodes.Ldloca_S, (byte)i);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldloc_S, (byte)i);
            }
        }

        LocalBuilder resultLocal = ilGenerator.DeclareLocal(typeof(object), false);
        ilGenerator.Emit(OpCodes.Call, source);
        if (source.ReturnType == typeof(void))
        {
            ilGenerator.Emit(OpCodes.Ldnull);
        }
        ilGenerator.Emit(OpCodes.Stloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Leave, exBlockLabel);

        ilGenerator.BeginFinallyBlock();
        for (int i = 0; i < paramInfos.Length; i++)
        {
            var paramInfo = paramInfos[i];
            bool paramIsByRef = paramInfo.ParameterType.IsByRef;
            var paramType = paramIsByRef ? paramInfo.ParameterType.GetElementType() : paramInfo.ParameterType;

            ilGenerator.Emit(OpCodes.Ldarg_0);
            ilGenerator.Emit(OpCodes.Ldc_I4, i);
            ilGenerator.Emit(OpCodes.Ldloc_S, (byte)i);
            if (paramType.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, paramType);
            }
            ilGenerator.Emit(OpCodes.Stelem, typeof(object));
        }
        ilGenerator.EndExceptionBlock();

        ilGenerator.Emit(OpCodes.Ldloc_S, resultLocal);
        ilGenerator.Emit(OpCodes.Ret);

        var invoker = (Func<object[], object>)invokerBuilder.CreateDelegate(typeof(Func<object[], object>));
        return invoker(parameters);
    }
}

public class Program
{
    static void Main()
    {
        object[] args = new object[1];
        try
        {
            MethodInfo targetMethod = typeof(Program).GetMethod("Method");
            targetMethod.InvokeStrictly(null, args);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.ToString());
            Console.WriteLine();
        }
        Console.WriteLine(args[0]);
        Console.ReadLine();
    }
    public static void Method(out string arg)
    {
        arg = "Hello";
        throw new Exception("Test Exception");
    }
}

简而言之,你没有做错任何事。 它是对invoke实现的限制。

在直接调用中使用ref ,本地值的引用将传递给方法。 通过调用,出于安全原因,只有在调用未引发异常时,才会生成副本并将其复制回本地引用。


对于长期答案......

所以,以你的例子为例,我创建了这个小提琴来查看IL代码。 这给了我们以下内容:

.method public hidebysig static void SampleMethod(string throwsException, [out] string& text) cil managed
 {
    // 
    .maxstack  2
    .locals init (bool V_0)
    IL_0000:  nop
    IL_0001:  ldarg.1             // Get argument 2
    IL_0002:  ldstr      "Hello"  // Get string literal
    IL_0007:  stind.ref           // store in reference address
    IL_0008:  ldarg.0
    IL_0009:  ldsfld     string [mscorlib]System.Boolean::TrueString
    IL_000e:  call       bool   [mscorlib]System.String::op_Equality(string, string)
    IL_0013:  ldc.i4.0
    IL_0014:  ceq
    IL_0016:  stloc.0
    IL_0017:  ldloc.0
    IL_0018:  brtrue.s   IL_0025

    IL_001a:  ldstr      "Test Exception"
    IL_001f:  newobj     instance void [mscorlib]System.Exception::.ctor(string)
    IL_0024:  throw

    IL_0025:  ret
} // end of method Program::SampleMethod

正如预期的那样,“Hello”的值在第二个(输出)参数的引用地址中设置。 这意味着抛出的异常与设置值没有区别。

现在使用调用没有直接调用。 我没有查找这部分的IL代码,但是源代码足以弄清楚最新情况。 首先调用Invoke方法:

public override Object Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
{
    object[] arguments = InvokeArgumentsCheck(obj, invokeAttr, binder, parameters, culture);    
    // [Security Check omitted for readability]   
    return UnsafeInvokeInternal(obj, parameters, arguments);
}

请注意,它调用InvokeArgumentsCheck ,它返回一个名为arguments的值数组。 该方法实现如下:

internal Object[] CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
{
    // copy the arguments in a different array so we detach from any user changes 
    Object[] copyOfParameters = new Object[parameters.Length];
    // [Code omitted for readability]
    for (int i = 0; i < parameters.Length; i++)
    {
        // [Code omitted for readability]
        copyOfParameters[i] = argRT.CheckValue(arg, binder, culture, invokeAttr);
    }

    return copyOfParameters;
}

该方法基本上创建了您指定的输入参数的副本(具有各种类型检查)。 正如您从方法中的注释中看到的那样,这样做是为了防止用户的任何更改影响数据将调用该方法。

最后我们看看UnsafeInvokeInternal 方法源如下所示:

private object UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
{
    if (arguments == null || arguments.Length == 0)
        return RuntimeMethodHandle.InvokeMethod(obj, null, Signature, false);
    else
    {
        Object retValue = RuntimeMethodHandle.InvokeMethod(obj, arguments, Signature, false);

        // copy out. This should be made only if ByRef are present.
        for (int index = 0; index < arguments.Length; index++)
            parameters[index] = arguments[index];

        return retValue;
    }
}

正如我们有论点,我们可以专注于“其他”部分。 通过传递参数来调用该方法,正如我们之前确定的那样,这些参数是所提供参数的副本。 调用完成后,参数值将被推回到源数组'Parameters'中。

在异常的情况下,这意味着代码在将“Hello”“推回”到输出参数之前被中止。 最有可能(但我一直无法检查)它确实改变了arguments数组中的复制值,我们无法访问它。

我会让你决定这是不是设计,监督,或者他们只是认为不应该有这样的用例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM