gpt4 book ai didi

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

转载 作者:太空狗 更新时间:2023-10-29 21:59:56 31 4
gpt4 key购买 nike

我想知道调用方法的 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/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com