I have some class. And by reflection i get it constructor and parameters count/type.
also i have builder for any type.
so i need to make
var constructor;
var params = constructor.GetParameters();
object[] args;
foreach( var param in params ) {
var type = param.Parametertype;
args[] += (object)Build<type>();
}
Activator.CreateInstance(Type, args);
The problem, that i can not pass the type of parameter as generic argument.
No, you'll need to use reflection to call the generic method too:
var constructor = ...;
var parameters = constructor.GetParameters();
object[] args = new object[parameters.Length];
// Adjust this for private methods etc
var buildMethod = typeof(ClassContainingBuild).GetMethod("Build");
for (int i = 0; i < args.Length; i++)
{
var genericBuild = buildMethod.MakeGenericMethod(parameters[i].ParameterType);
// Adjust appropropriately for target etc
args[i] = genericBuild.Invoke(this, null);
}
Related
I've got a slightly modified class of this answer in order to dynamically call TryParse of various types (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;
}
}
Unfortunately this throws an AccessViolationException and after checking the code a bit more in detail I'm still unsure why.
This code also "runs" in another project where it seems like the return value is not consistent.
Sometimes it just returns false when the actual parsing via TryParse is successful.
It sounds like undefined behaviour, but I can't seem to find the issue(s).
Here's a sample code of the AccessViolationException AND the undefined behaviour (remove float value from array for 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;
}
}
What is the problem of the UB ?
Why the AccessViolationException ? Another problem or related to UB ?
If I create a DynamicMethod from inside a class method how can I call another method of my class from the DynamicMethod Delegate? I need somehow to capture the this reference in the DynamicMethod code.
But I can't find an overladed version of ILGenerator.Emit which takes an object as parameter.
The code is so far:
void CallOpc(string name, object[] inp, object[] outp)
{
//...
}
public D CreateDelegate<D>(string opcName) where D : class
{
var dType = typeof(D);
var invoke = dType.GetMethod("Invoke");
var parameters = invoke.GetParameters();
var paramTypes = parameters.Select(p => p.ParameterType).ToArray();
DynamicMethod dm = new DynamicMethod(
opcName,
invoke.ReturnType,
paramTypes,
true);
var inp = parameters.Where(p => !p.IsOut).Select(p => p.ParameterType).ToList();
var outp = parameters.Where(p => p.IsOut).Select(p => p.ParameterType).ToList();
if (invoke.ReturnType != typeof(void))
{
outp.Insert(0, invoke.ReturnType);
}
ILGenerator il = dm.GetILGenerator();
LocalBuilder invar = il.DeclareLocal(typeof(object[]));
LocalBuilder outvar = il.DeclareLocal(typeof(object[]));
il.Emit(OpCodes.Ldc_I4, inp.Count);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, invar);
for (int i = 0; i < inp.Count; i++)
{
il.Emit(OpCodes.Ldloc, invar);
il.Emit(OpCodes.Ldc_I4, i);
int j = Array.IndexOf(paramTypes, inp[i]);
il.Emit(OpCodes.Ldarg, j);
if (!inp[i].IsClass)
{
il.Emit(OpCodes.Box, inp[i]);
}
il.Emit(OpCodes.Stelem_Ref);
}
il.Emit(OpCodes.Ldc_I4, outp.Count);
il.Emit(OpCodes.Newarr, typeof(object));
il.Emit(OpCodes.Stloc, outvar);
il.Emit(OpCodes.Ldarg_0); // <- push this on the evaluation stack ???
il.Emit(OpCodes.Ldstr, opcName);
il.Emit(OpCodes.Ldloc, invar);
il.Emit(OpCodes.Ldloc, outvar);
MethodInfo callOpcMeth = GetType().GetMethod("CallOpc", BindingFlags.Instance | BindingFlags.NonPublic);
il.Emit(OpCodes.Callvirt, callOpcMeth);
for (int o = 0; o < outp.Count; o++)
{
// TODO: handle out params and return value
}
il.Emit(OpCodes.Ret);
return (D)(object)dm.CreateDelegate(dType);
}
My problem is the line marked with ???
How to reference the this pointer from a DynamicMethod?
At low level, methods do not have a specific this pointer, instead the first argument of the method is used as this.
To reference the this pointer, here is what you have to do:
Prepend a new argument to your argument list, matching the type you want to use as this
Use that argument as this wherever you want
Create the delegate using DynamicMethod.CreateDelegate(Type,Object) to bind the first parameter to your object: return (D)(object)dm.CreateDelegate(dType, this);
Note that creating dynamic methods is expensive. If you generate the same DynamicMethod for multiple instances, you should cache the method itself, and create delegates using the cached method, but with different target parameter.
I use reflection to get FieldInfos in a class and assign to them with FieldInfo.SetValue. It works fine when i assign primitive types (i use Convert.ChangeType to convert from object to type) but doesn't work if the field is an array. It works if i use Array.Cast but the type is only known at runtime so i cant cast.
I saw lots of topics on this but none of them worked so far.
I get this exception:
ArgumentException: Object type System.Object[] cannot be converted to target type: System.Single[]
I know why it happens i just can't find a way to convert the data. Any ideas?
EDIT: relevant code:
public static object Deserialize (string path){
string[] lines = File.ReadAllLines(path);
ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
object newObj = ci.Invoke(new object[] {});
Type type = Type.GetType(lines[0], true);
FieldInfo[] fields = type.GetFields(BindingFlags.Public);
for (int i = 1; i < lines.Length; i++){
FieldInfo thisField = currentType.GetField(lines[i]);
if (thisField != null) {
if (line != "") {
if (fieldType == typeof(string)) {
thisField.SetValue(currentObject, line);
thisField = null;
}
else if (fieldType.IsPrimitive) {
val = Convert.ChangeType(line, fieldType);
thisField.SetValue(currentObject, val);
thisField = null;
}
else if (fieldType.IsArray){
string[] values = ReadUntil(']');
//ReadUntil just returns the values as string[] as read from text file
object[] newValues = Convert.ChangeType(values, fieldType);
thisField.SetValue(newObj, newValues);
}
}
}
}
}
return newObj;
}
You can use something like this
else if (fieldType.IsArray)
{
string[] values = ReadUntil(']');
var elementType = fieldType.GetElementType();
if (elementType == typeof(string))
thisField.SetValue(newObj, values);
else
{
var actualValues = Array.CreateInstance(elementType, values.Length);
for (int i = 0; i < values.Length; i++)
actualValues.SetValue(Convert.ChangeType(values[i], elementType), i);
thisField.SetValue(newObj, actualValues);
}
}
Type.GetElementType method is used to retrieve the type of the array elements, then Array.CreateInstance method to create an array of the desired type, and finally Array.SetValue method to populate the new array elements with the converted values.
I have a PropertyInfo.SetValue that has a dynamic set. Meaning the value to be set is not known.
I've have a method like this i got from the internet.
private static Action<object, object> CreateSetAccess(MethodInfo method)
{
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
return expr.Compile();
}
What this does is create a expression and compile it, but the objects get converted using the parameter types.
I consume it like this.
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
What happens is that this seems to be slower that the PropertyInfo.SetValue
Here is my benchmark
var xpathNavigator = XmlHelper.CreateXPathDocument(serviceResponse).CreateNavigator();
foreach (var propertyInformation in propertyInformationSource)
{
// Gets the node using the NodePath provided in the Attribute
var attr = propertyInformation.Value;
var pathValue = xpathNavigator.SelectSingleNode(attr.NodePath);
if (pathValue == null)
continue;
object valueToSet = null;
var property = propertyInformation.Key;
if (propertyInformation.Value.ShouldDeserialize)
valueToSet = serializationHelper.Deserialize(property.PropertyType, pathValue.OuterXml, attr.CustomRoot);
else
valueToSet = Convert.ChangeType(pathValue.Value, property.PropertyType);
// this line is only added for the testing for it to be JITd
var method = CreateSetAccess(property.GetSetMethod());
method(response, valueToSet);
property.SetValue(response, valueToSet);
// end
TimeSpan fastSet, setValue;
const int COUNT = 100000;
var watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
var method2 = CreateSetAccess(property.GetSetMethod());
method2(response, valueToSet);
}
watch.Stop();
fastSet = watch.Elapsed; // result {00:00:08.8500760}
watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
property.SetValue(response, valueToSet);
}
watch.Stop();
setValue = watch.Elapsed; // result {00:00:00.0263953}
}
I'm wondering why this happens? I'm guessing because I'm always creating a new expression, but how can i make it not create a new object and make it get cached?
If compiling a fresh expression each time was faster then the Reflection API would just do this internally. Therefore, it is not. This technique only works if you reuse the same compiled code many times.
so there is no way to make the expression adjust at runtime based on the methodinfo supplied?
Reflection does this and that's what's making it so slow. Keep a cache of compiled methods. For example in a Dictionary<MethodInfo, Action<object, object>> with a suitable comparer.
Create Dictionary<MethodInfo, Action<object, object>> actions field in your class.
You can remove the static modifier from CreateSetAccess. And then add the following code in it:
Action<object, object> action = null;
if(actions.TryGetValue(method, out action))
{
return action;
}
var obj = Expression.Parameter(typeof(object), "o");
var value = Expression.Parameter(typeof(object));
Expression<Action<object, object>> expr =
Expression.Lambda<Action<object, object>>(
Expression.Call(
Expression.Convert(obj, method.DeclaringType),
method,
Expression.Convert(value, method.GetParameters()[0].ParameterType)),
obj,
value);
expr.Compile();
cache.Add(method, expr);
return expr;
}
How to get the ParameterInfo of a function with variable number of params?
The problem is when I call the method
MyFunction(object o1, out object o2);
I can get the parameterInfo of sendData but not the o1 and o2 object.
protected object[] MyFunction(params object[] sendData)
{
StackTrace callStack = new StackTrace(0, false);
StackFrame callingMethodFrame = callStack.GetFrame(0);
MethodBase callingMethod = callingMethodFrame.GetMethod();
ParameterInfo[] parametersInfo = callingMethod.GetParameters();
List<object> inParams = new List<object>();
List<object> outParams = new List<object>();
for (int i = 0; i < sendData.Length; i++)
{
object value = sendData[i];
ParameterInfo info = parametersInfo[parametersInfo.Length - sendData.Length + i];
if (info.IsOut)
{
outParams.Add(value);
}
else
{
inParams.Add(value);
}
}
..........
}
Thanks in advance for helping me.
Arnaud
'params' is just C# syntactic sugar. In fact, at metadata .NET level, there is only one parameter named "sendData" with a specific "ParamArray" attribute set.