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

标签 c# reflection

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

当调用方法时没有抛出异常,参数中接收到值,但是当调用的方法中抛出异常时,我没有获取到值。不经过反射,直接调用方法,接收到值。

我是不是做错了什么或者这是 .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");
    }
}

最佳答案

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

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


对于长答案...

因此,以您创建的示例为例 this fiddle查看 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”的值在第二个(输出)参数的引用 地址中设置。这意味着抛出的异常对是否设置值没有影响。

现在使用 invoke 没有直接调用。我没有查找这部分的 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”“推回”到我们的输出参数之前被中止。很可能(但我无法检查)它确实更改了参数数组中的复制值,我们只是无法访问它。

我会让您决定这是设计使然、疏忽大意,还是他们只是认为无论如何都不应该有这个用例。

关于c# - 在调用的方法中抛出异常时,如何使用 MethodInfo.Invoke 获取作为引用传递的参数值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48532028/

相关文章:

c# - 根据分辨率缩放屏幕上的所有 Sprite

c# - 反序列化具有可变结构的 XML 文件

c# - 如何创建具有多个方法调用的 ExpressionTree

java - java中的动态方法返回类型

c# - 打印 BizTalk Orchestration 的硬拷贝

c# - Signalr - 重写发送消息的方法

Java-声明用户选择的类型的对象

java - POJO 对象的 JAX-RS 通用工厂

c# - 从 property 属性获取数据

c# - 画笔算法/颜色褪色模式 - 需要建议