У меня есть немного измененный класс этот ответ для динамического вызова TryParse различных типов (char
, int
, long
).
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?
Convert.ChangeType
не самая быстрая вещь под солнцем, и вам нужно перехватывать исключения (поэтому это медленно, если вы ожидаете, что данные будут содержать много неразборчивых вещей), но синтаксический анализ в первую очередь не быстрый. В противном случае количество типов, фактически реализующих.TryParse
, достаточно мало, чтобы вы могли иметьswitch
наTypeCode
с некоторыми предварительно созданными делегатами. Генерация кода путем отражения удобна, если упаковка и преобразование имеют значительные накладные расходы, но опять же, если вы анализируете текст, это, вероятно, не так. - person Jeroen Mostert   schedule 12.12.2018il.Emit(OpCodes.Ldelem_Ref)
), а не значения? - person usr   schedule 12.12.2018switch
для этого фрагмента кода. К сожалению, проблема в других случаях использования остается. - person Blacktempel   schedule 13.12.2018Ldnull
, я удалю этот кусок. Создание недопустимого IL, скорее всего, верно... У меня пока нет большого опыта в этом, поэтому я не уверен, в чем проблема. Что касается ссылок - что мне делать здесь вместо этого? - person Blacktempel   schedule 13.12.2018DynamicMethod
к полнымAssemblyBuilder
иMethodBuilder
. Затем, после создания методов, используйтеAssemblyBuilder.Save()
, чтобы вывести результат на диск, и используйтеpeverify
, чтобы выявить ошибки IL. Поднимайтесь и повторяйте, пока не получите правильное поколение. - person thehennyy   schedule 13.12.2018