我想知道被调用方法的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");
}
}
答案 0 :(得分:0)
简而言之,你没有做错任何事。它是对invoke实现的限制。
在直接调用中使用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”的值在第二个(输出)参数的引用地址中设置。这意味着抛出的异常与设置值没有区别。
现在使用调用没有直接调用。我没有查找这部分的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'。
在异常的情况下,这意味着代码在被“推回”到输出参数之前被中止。它很可能(但我一直无法检查)它确实改变了arguments数组中的复制值,我们无法访问它。
我会让你决定这是不是设计,监督,或者他们只是认为不应该有这样的用例。