Invoking Generic method with params parameter through reflection - c#

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);

Related

Why does "params object[]" need passing as "new object[] { parameters }" for Com Method?

I've found lots of info on passing parameters to methods and found that having the method use params Object[] args to be a great (and sometimes only) way to do this.
i.e. sample method:
public void someMethod(params object[] args)
{
//do something
}
But when passing the parameter object like this to a Com Method, it doesn't work:
object[] passParameters = new object[2];
passParameters[0] = "test1";
passParameters[1] = "test2";
//Calling method compiled with with CodeDom
MethodInfo method = classObject.GetType().GetMethod("someMethod");
object dynamicOutput = method.Invoke(classObject, passParameters);
For it to work, I have found that I need to pass the parameter object as a new Object:
object dynamicOutput = method.Invoke(classObject,(new object[] { passParameters });
Can anyone please explain to me why I need to do this and what the reasoning is behind it? Thanks.
They don't. You can just use them as
someMethod("test1", "test2", ... )
BUT. If you're creating an array beforehand, it has to be passed differently, as you encountered
I've come to realise that the original passParameters object in the example contains two elements, as we know:
passParameters[0] = "test1";
passParameters[1] = "test2";
However, when the array is passed to the Com method, it requires being passed as so:
new object[] { passParameters })
My understanding of this is, that it creates a new object array which has put the existing 'passParameters' array into its first element [0] (effectively creating a new 2d array):
passParameters[0][0] = "test1";
passParameters[0][1] = "test2";
The method MethodInfo class in my question, required a single object passed to the Invoke method, which meant that the original 'passParameters' object with its two elements had too many arguments. By passing it inside a new object, this passes all of the elements as a single object, but containing an array of those parameters.

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" } });

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));

passing static reflection information to static generic methods

EDIT: the class/method that i'm trying to run this inside is static and therefore i'm unable to pass this into the generic.Invoke
I have a static Data Access Class that i use to automatically parse data from various sources.
i was starting to re-factor it when i ran into a problem.
Im tring to pass a Type to a Generic method via reflection,
(the method then parses the type and returns the Type with a value)
my code currently looks like
Type type1 = typeof( T );
var item = (T)Activator.CreateInstance( typeof( T ), new object[] { } );
foreach (PropertyInfo info in type1.GetProperties())
{
Type dataType = info.PropertyType;
Type dataType = info.PropertyType;
MethodInfo method = typeof( DataReader ).GetMethod( "Read" );
MethodInfo generic = method.MakeGenericMethod( dataType );
//The next line is causing and error as it expects a 'this' to be passed to it
//but i cannot as i'm inside a static class
generic.Invoke( this, info.Name, reader );
info.SetValue(item,DataReader.Read<dataType>(info.Name, reader ) , null);
}
I guess DataReader.Read is the static method, right?
Therefore, change the error line like below, since you are calling the static method. There is not object, so you just pass null into Invoke method:
var value = generic.Invoke( null, new object[] {info.Name, reader} );
The type parameter to a generic method isn't an instance of Type; you can't use your variable in this way. However, you can use reflection to create the closed-generic MethodInfo you require (that is, with the type parameter specified), which would look something like this:
// this line may need adjusting depending on whether the method you're calling is static
MethodInfo readMethod = typeof(DataReader).GetMethod("Read");
foreach (PropertyInfo info in type1.GetProperties())
{
// get a "closed" instance of the generic method using the required type
MethodInfo genericReadMethod m.MakeGenericMethod(new Type[] { info.PropertyType });
// invoke the generic method
object value = genericReadMethod.Invoke(info.Name, reader);
info.SetValue(item, value, null);
}

Why params does not accept generic types?

Here is my scenario,
A function :
public String StringConcat(params String[] parameter)
{
String l_strReturnValue = String.Empty;
for (Int32 l_nIndex = 0; l_nIndex < parameter.Length; l_nIndex++)
{
l_strReturnValue += parameter[l_nIndex];
}
return l_strReturnValue;
}
And i'm calling it like
List<String> l_lstTest = new List<string> { "A", "B", "C" };
String l_strString = StringConcat(l_lstTest.Select(X => X).ToArray());
it returns the value as "ABC"
But its showing error if I call the function without type convrsion like
String l_strString = StringConcat(l_lstTest.Select(X => X));
So how to use the function without conversion ?
Note 1 :
In XDocument Add method - they have used like params, but there is no such needs of type conversions.
Note 2 :
The purpose of this post is not to add the strings, just want to learn more about the limits of params.
The return type of Select(X => X) will be IEnumerable<string> - not an array. So you need another overload:
public String StringConcat(IEnumerable<string> parameter)
You'd probably make the array overload call this overload.
(And yes, obviously you'd want to use StringBuilder instead of repeated string concatenation - and foreach instead of a for loop.)
Note that the relevant XDocument.Add overload takes a params Object[] parameter, not params String[] - and LINQ to XML works such that if you try to add something which is itself enumerable, it's as if you added each item in turn. That's not part of the language - it's part of the implementation. So if you call:
doc.Add(someStringArray.Select(x => x))
that will actually just call XDocument.Add(object) which will notice that the argument implements IEnumerable.
The limits of the params keyword is that the parameter must be an array type.
l_lstTest.Select(X => X) is an IEnumerable<string>, not an array, so it does not match the formal parameter type. It doesn't work for the same reason it would not work if you tried to pass a plain int.
To make it work, you should add another overload of the method:
public String StringConcat(IEnumerable<string> parameter)
{
// your code here
}
It's a limitation of params by design, because if you were using
public String StringConcat(params object[] parameter)
and you called with IEnumerable, you couldn't figure out if the parameter is a single parameter or it should iterate on the enumerable. For instance, without this limitation, in
StringConcat( l_lstTest.Select(X => X) )
the IEnumerable should be an element of the list of parameters or it's the list of parameters?

Categories