How to pass runtime argument variable in Expression.Call? [duplicate] - c#

This question already has answers here:
Calling (params object[]) with Expression[]
(2 answers)
Closed 9 years ago.
I'm missing something trivial here. Say I have a method like this:
abstract class C
{
public static void M(Type t, params int[] i)
{
}
}
I'm learning expression trees and I need to build a delegate that calls this method with some predefined arguments. The problem is that I don't know to choose the correct overload and pass arguments of Expression.Call.
I want to achieve this:
//I have other overloads for M, hence I need to specify the type of arugments
var methodInfo = typeof(C).GetMethod("M", new Type[] { typeof(Type), typeof(int[]) });
//this is the first argument to method M, not sure if I have chosen the right expression
var typeArgumentExp = Expression.Parameter(someType);
var intArrayArgumentExp = Enumerable.Repeat(Expression.Constant(0), 3);
var combinedArgumentsExp = new Expression[] { typeArgumentExp }.Concat(intArrayArgumentExp);
var call = Expression.Call(methodInfo, combinedArgumentsExp);
At the Expression.Call line I get:
An unhandled exception of type 'System.ArgumentException' occurred in
System.Core.dll
Additional information: Incorrect number of arguments supplied for
call to method 'Void M(System.Type, Int32[])'
Where have I gone wrong?

The params keyword does not do anything at runtime. When you call C.M(t, 1, 2, 3), the compiler transforms this to C.M(t, new int[] { 1, 2, 3 }). In this case, you are performing parts of the job of the compiler, and this is one transformation that becomes your responsibility. You should explicitly create the array, and call C.M with exactly two arguments.

I solved it like this (shown here Calling (params object[]) with Expression[]):
//I have other overloads for M, hence I need to specify the type of arugments
var methodInfo = typeof(C).GetMethod("M", new Type[] { typeof(Type), typeof(int[]) });
//I fixed this issue where the first argument should be typeof(Type)
var typeArgumentExp = Expression.Parameter(typeof(Type));
var intArrayArgumentExp = Expression.NewArrayInit(typeof(int), Enumerable.Repeat(Expression.Constant(0), 3));
var combinedArgumentsExp = new Expression[] { typeArgumentExp }.Concat(intArrayArgumentExp);
var call = Expression.Call(methodInfo, combinedArgumentsExp);
Expression.NewArrayInit does the trick. Thanks hvd for his direction.

Related

Invoking Generic method with params parameter through reflection

I am trying to invoke a generic methods that accepts a single params parameter through reflection.
When I picked it to be non generic passing an object[] item seemed to be sufficient but when I reqired to call a generic method it does not work anymore.
var type = typeof (ClassWithGenericMethod);
var method = type.GetMethod("GenericMethod", BindingFlags.Instance | BindingFlags.Public);
var genericMethod = method.MakeGenericMethod(typeof(object));
var result = (bool)genericMethod.Invoke(new ClassWithGenericMethod(), new object[]{"param"});
Assert.IsTrue(result);
The called class:
public class ClassWithGenericMethod
{
public bool GenericMethod<T>(params string[] input)
{
return input.Length == 1;
}
}
The code fails before the assert with the following exception:
Object of type 'System.String' cannot be converted to type
'System.String[]'.
Try repleace new object[]{"param"} with new object[] { new[] { "param" } }.
Here new object[] is the array of parameters, and the first parameter should be a string[], but in your code, you use a string, hence the exception.
When using reflection to call a method that has a params keyword specified, you should just ignore the params keyword. You will need to specify an array of the appropriate type and pass that as the argument.
In your case, instead of passing a single parameter of "param", you need to pass an array of string containing a single item.
The params keyword actually affects how the method caller is compiled. When the compiler sees that the called method specifies "params", it builds the appropriate type of array and passes it. The compiler "magically" turns the following
string result = string.Concat("A","B","C","D");
into basically a compiled version of the following
string[] x = {"A", "B", "C", "D"};
string result = string.Concat(x);

C# - How to use Reflection to invoke static method with variable number of parameters

I want to identify the following method via reflection:
String.Concat(params string[] args);
This is what I have tried:
MethodInfo concatMethod = typeof(string).GetMethod("Concat", new Type[] { typeof(string[]) });
The method is being identified correctly, but when I try to invoke it:
object concatResult = concatMethod.Invoke(null, new object[] { "A", "B" });
I get the following exception:
TargetParameterCountException: Parameter count mismatch.
Also note that I am passing null as the instance argument to the Invoke method, this is because the method is static and therefore an instance is not needed. Is this approach correct?
PS: I want to simulate the following call:
String.Concat("A", "B");
Each element of the input array is a parameter to the method. The overload of Concat you have takes a single string[] argument so you need:
object concatResult = concatMethod.Invoke(null, new object[] { new string[] { "A", "B" } });

SelectMany cannot be inferred from the usage [duplicate]

This question already has an answer here:
SelectMany() Cannot Infer Type Argument -- Why Not?
(1 answer)
Closed 7 years ago.
I get the following error when I try to compile my code:
The type arguments for method
'System.Linq.Enumerable.SelectMany(System.Collections.Generic.IEnumerable,
System.Func>)'
cannot be inferred from the usage. Try specifying the type arguments
explicitly.
List<RowEntry> entries = ...
List<RowArgument> arguments = ...
var argumentsVal = entries.SelectMany((RowEntry entry) =>
(IEnumerable<RowArgumentVal>)arguments.SelectMany((RowArgument arg) =>
new RowArgumentVal()
{
Action = "X"
, EntryID = entry.ID
, ArgID = arg.ID
, Val_BIT = true
, Val_DATE = new DateTime(2014, 01, 04)
, Val_DATETIME = new DateTime(2014, 01, 04)
, Val_DECIMAL = 4.6M
, Val_INT = 88
, Val_TIME = new TimeSpan(6, 0, 0)
}
).Cast<RowArgumentVal>()).Cast<RowArgumentVal>().ToList();
I don't get how I can "type" this even further...
The problem is that the inner SelectMany isn't applicable there, and you probably meant Select.
var argumentsVal = entries.SelectMany(entry =>
arguments.Select(arg => new RowArgumentVal())).ToList();
Each entry will be mapped into an IEnumerable<RowArgumentVal> according to the arguments.
Imagine the outer SelectMany was a simple Select, and it would generate List<IEnumerable<RowArgumentVal>>. But because it is SelectMany, it will "flatten" the result into a simple List<RowArgumentVal>.
The SelectMany method expects a mapping to IEnumerable<T> - not T. Your original code would be valid if RowArgumentVal just happened to implement the IEnumerable<T> interface, which I suppose isn't the case.
That seems to be a cartesian product from both lists since there is no relation between them. You may want to join them but it's not exactly clear how.
Here is a more readable and compiling way to produce a cartesian product:
var query = from entry in entries
from argument in arguments
select new RowArgumentVal
{
Action = "X", EntryID = entry.ID, ArgID = argument.ID, // ...
};
List<RowArgumentVal> argumentsVal = query.ToList();
If you want a cartesian product try doing the following
var argumentsVal = from e in entries
from a in arguments
select new RowArgumentVal(...)
As an aside, the "further typing" you could have done would be to give the type arguments to the generic method calls. In particular, if you had changed the second SelectMany to SelectMany<RowArgument, RowArgumentVal> you would have got the errors
Cannot implicitly convert type RowArgumentVal to System.Collections.Generic.IEnumerable<RowArgumentVal>. An explicit conversion exists (are you missing a cast?)
Cannot convert lambda expression to delegate type System.Func<RowArgument,int,System.Collections.Generic.IEnumerable<RowArgumentVal>> because some of the return types in the block are not implicitly convertible to the delegate return type
which would maybe have led you to the other answers here - that you were trying to call a method that expected a sequence, but you were giving it a single object.
(Or in trying to decide which type arguments to add, you would have realised what was wrong sooner.)

How do you call a generic method with out parameters by reflection?

Suppose I have a class like this, containing a generic method with an out parameter:
public class C
{
public static void M<T>(IEnumerable<T> sequence, out T result)
{
Console.WriteLine("Test");
result = default(T);
}
}
From reading the answers to a couple of other questions (How to use reflection to call generic Method? and Reflection on a static overloaded method using an out parameter), I thought I might be able to invoke the method via reflection as follows:
// get the method
var types = new[] { typeof(IEnumerable<int>), typeof(int).MakeByRefType() };
MethodInfo mi = typeof(C).GetMethod(
"M", BindingFlags.Static, Type.DefaultBinder, types, null);
// convert it to a generic method
MethodInfo generic = mi.MakeGenericMethod(new[] { typeof(int) });
// call it
var parameters = new object[] { new[] { 1 }, null };
generic.Invoke(null, parameters);
But mi is coming back null. I've tried using object instead of int in the types array but that doesn't work either.
How can I specify the types (needed for the out parameter) for a generic method before the call to MakeGenericMethod?
This will let you call the method:
MethodInfo mi = typeof(C).GetMethod("M");
MethodInfo generic = mi.MakeGenericMethod(new[] { typeof(int) });
var parameters = new object[] { new[]{1},null};
generic.Invoke(null, parameters);
And to get the out parameter:
Console.WriteLine((int)parameters[1]); //will get you 0(default(int)).
I'm still interested to know what the syntax is for specifying an array of template types, or if it's not possible
I don't think it's possible to pass that kind of detailed type specification to GetMethod[s]. I think if you have a number of such Ms to look through, you have to get them all and then filter by the various properties of the MethodInfos and contained objects, eg as much of this as is necessary in your particular case:
var myMethodM =
// Get all the M methods
from mi in typeof(C).GetMethods()
where mi.Name == "M"
// that are generic with one type parameter
where mi.IsGenericMethod
where mi.GetGenericArguments().Length == 1
let methodTypeParameter = mi.GetGenericArguments()[0]
// that have two formal parameters
let ps = mi.GetParameters()
where ps.Length == 2
// the first of which is IEnumerable<the method type parameter>
where ps[0].ParameterType.IsGenericType
where ps[0].ParameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
where ps[0].ParameterType.GetGenericArguments()[0] == methodTypeParameter
// the second of which is ref <the method type parameter>
where ps[1].ParameterType.IsByRef
where ps[1].ParameterType.GetElementType() == methodTypeParameter
select mi;
You've passed parameters that will find M<T>(IEnumerable<int>, ref int).
You need to find M(IEnumerable<T>, ref T) (the distinction between ref and out exists only in the C# language; reflection only has ref).
I'm not sure how to pass that; you may need to loop through all methods to find it.
On an unrelated note, you need to pass more BindingFlags:
BindingFlags.Public | BindingFlags.Static
This is a well known-problem; to find the method, you need to know its type parameter, but you can't know its type parameter without knowing the method first...
An obvious but inelegant solution is to loop through all methods until you find the right one.
Another option is to take advantage of the Linq Expression API:
public static MethodInfo GetMethod(Expression<Action> expr)
{
var methodCall = expr.Body as MethodCallExpression;
if (methodCall == null)
throw new ArgumentException("Expression body must be a method call expression");
return methodCall.Method;
}
...
int dummy;
MethodInfo mi = GetMethod(() => C.M<int>(null, out dummy));

C# construct lambda using Expression.Call doesn't like certain types as params?

For various reasons I'm constructing a C# lambda dynamically using the expression tree facilities. e.g. I can make a Func<string,bool> at runtime as shown in the following snippet.
public static bool myMethod( object obj ) { … }
// Construct a Func<string,bool>
var myMethod = GetType().GetMethod("myMethod");
var lambdaParams = new ParameterExpression[] {Expression.Parameter(typeof (string))};
var callMyMethod = Expression.Call(myMethod, lambdaParams);
var lambda = Expression.Lambda(typeof(Func<string,bool>), callMyMethod, lambdaParams);
var del = (Func<string,bool>)lambda.Compile();
del("foo"); // works
However if I use the same code to try to make a Func<int,bool> or a Func<DateTime,bool> it blows up where indicated with the following strange exception:
// Construct a Func<DateTime,bool> or perhaps a struct type fails... why?
var myMethod = GetType().GetMethod("myMethod");
var lambdaParams = new ParameterExpression[] {Expression.Parameter(typeof (DateTime))};
var callMyMethod = Expression.Call(myMethod, lambdaParams); // Blows up here…
System.ArgumentException: Expression of type 'System.DateTime' cannot be used for parameter of type 'System.Object' of method 'Boolean myMethod(System.Object)'
So, string works and List<string> works but int32 does not work nor does DateTime. What is going on? I don't know how the deep internals of C# work but I am guessing it's due to int really being handled as a primitive and maybe DateTime (being a struct) as well...
Any help with this would be greatly appreciated.
thanks,
Pat
As I understand it, Expression.Call doesn't perform auto-boxing of value-type arguments. I'm unable to find any documentation to that effect, but it is mentioned on this forum page.
One workaround would be to explicitly do the boxing conversion in the expression with Expression.TypeAs.
Creates a UnaryExpression that
represents an explicit reference or
boxing conversion where null is
supplied if the conversion fails.
In your case, this should work:
var boxedParams = lambdaParams.Select(p => Expression.TypeAs(p, typeof(object)))
.ToArray();
var callMyMethod = Expression.Call(myMethod, boxedParams);
(You don't need the fancy lambdas if there's only one parameter)
Depending on the real usage, you may have to check if the boxing conversion is necessary depending on whether the type(s) in question is(are) value-type(s).
Check this out: you have to box the DateTime, since DateTime isnt' a reference type!
// Construct a Func<DateTime,bool>
var myMethod = typeof(Program).GetMethod("myMethod");
var param = Expression.Parameter(typeof(DateTime));
var boxy = Expression.TypeAs(param, typeof(object));
var callMyMethod = Expression.Call(myMethod, boxy);
var lambda = Expression.Lambda(typeof(Func<DateTime, bool>), callMyMethod, new ParameterExpression[] { param });
var del = (Func<DateTime,bool>)lambda.Compile();
del(DateTime.Now); // works

Categories