为了动态调用各种类型的TryParse(char
,int
,long
),我对this answer进行了稍微修改。
public delegate TRet DynamicMethodDelegate<TRet>(object target, params object[] args);
public delegate void DynamicMethodDelegate(object target, params object[] args);
public class DynamicMethodDelegateFactory
{
public static TDelegate CreateMethodCaller<TDelegate>(MethodInfo method)
where TDelegate : class
{
ParameterInfo[] parameters = method.GetParameters();
Type[] args = { typeof(object), typeof(object[]) };
DynamicMethod dynam =
new DynamicMethod
(
method.Name
, method.ReturnType
, args
, typeof(DynamicMethodDelegateFactory)
, true
);
//Add parmeter attributes to the new method from the existing method
for (int i = 0; i < parameters.Length; i++)
{
dynam.DefineParameter
(
i,
parameters[i].Attributes,
parameters[i].Name
);
}
ILGenerator il = dynam.GetILGenerator();
// If method isn't static push target instance on top of stack.
if (!method.IsStatic)
{
// Argument 0 of dynamic method is target instance.
il.Emit(OpCodes.Ldarg_0);
}
// Lay out args array onto stack.
LocalBuilder[] locals = new LocalBuilder[parameters.Length];
List<LocalBuilder> outOrRefLocals = new List<LocalBuilder>();
for (int i = 0; i < parameters.Length; i++)
{
//Push args array reference onto the stack, followed
//by the current argument index (i). The Ldelem_Ref opcode
//will resolve them to args[i].
if (!parameters[i].IsOut)
{
// Argument 1 of dynamic method is argument array.
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, i);
il.Emit(OpCodes.Ldelem_Ref);
}
// If parameter [i] is a value type perform an unboxing.
Type parameterType = parameters[i].ParameterType;
if (parameterType.IsValueType)
{
il.Emit(OpCodes.Unbox_Any, parameterType);
}
}
//Create locals for out parameters
for (int i = 0; i < parameters.Length; i++)
{
if (parameters[i].IsOut)
{
locals[i] = il.DeclareLocal(parameters[i].ParameterType.GetElementType());
il.Emit(OpCodes.Ldloca, locals[locals.Length - 1]);
}
}
if (method.IsFinal || !method.IsVirtual)
{
il.Emit(OpCodes.Call, method);
}
else
{
il.Emit(OpCodes.Callvirt, method);
}
for (int idx = 0; idx < parameters.Length; ++idx)
{
if (parameters[idx].IsOut || parameters[idx].ParameterType.IsByRef)
{
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldc_I4, idx);
il.Emit(OpCodes.Ldloc, locals[idx].LocalIndex);
if (parameters[idx].ParameterType.GetElementType().IsValueType)
il.Emit(OpCodes.Box, parameters[idx].ParameterType.GetElementType());
il.Emit(OpCodes.Stelem_Ref);
}
}
if (method.ReturnType != typeof(void))
{
// If result is of value type it needs to be boxed
if (method.ReturnType.IsValueType)
{
il.Emit(OpCodes.Box, method.ReturnType);
}
}
else
{
il.Emit(OpCodes.Ldnull);
}
il.Emit(OpCodes.Ret);
return dynam.CreateDelegate(typeof(TDelegate)) as TDelegate;
}
}
不幸的是,这引发了AccessViolationException
,在更详细地检查了代码之后,我仍然不确定为什么。
此代码还会在似乎返回值不一致的另一个项目中“运行”。
有时,通过false
的实际解析成功后,它仅返回TryParse
。
听起来像是不确定的行为,但我似乎找不到问题。
这是AccessViolationException
和未定义行为的示例代码(从UB的数组中删除浮点值)。
static void Main(string[] args)
{
var arr = new object[] { 'A', (byte)1, (short)2, 3, 4L, 5M, 6.0, 7.0F, "8" };
for (int i = 0; i < 100000; i++)
{
foreach (var item in arr)
ParseTest(item);
}
int a = 1;
int b = a;
}
static Type StringType = typeof(string);
static bool ParseTest(object data)
{
var type = data.GetType();
if (type == StringType)
return true;
else
{
var mi = type.GetMethod(nameof(int.TryParse), new[] { StringType, type.MakeByRefType() });
var dmd = DynamicMethodDelegateFactory.CreateMethodCaller<DynamicMethodDelegate<bool> >(mi);
dynamic dummy = null;
var args = new object[] { data, dummy };
var ok = dmd(null, args);
return ok;
}
}
AccessViolationException
?另一个问题或与UB有关?