Reputation: 802
I have a type instance t
:
public void Action(T1in ps, ..., TNin ps, out T1out,..., out TKout)
and two Dictionaries In
and Out
{Type, instance}
inside.I assume that there are no other arguments required in order to call an Action
of t
.
So how to call a function with multiple inputs and outputs using Reflection/Dynamitey knowing their types yet not their order?
Upvotes: 0
Views: 630
Reputation: 12739
When you invoke a method via reflection, you pass an object[]
containing all of the parameters. For out
parameters, you leave the corresponding value in the array as null
. Once the method has completed you can retrieve the value from the corresponding position in the array.
To demonstrate this, let's assume the following two classes:
public class A
{
public string Something { get; set; }
}
public class B
{
public int Id { get; set; }
public override string ToString() => "[B] Id=" + Id; // for Console.WriteLine
}
We are going to invoke the following static method which includes three input parameters and one out
parameter (of type B
):
public static void MyMethod(int i, string x, A a, out B b)
{
Console.WriteLine("In Method - i={0}", i);
Console.WriteLine("In Method - x={0}", x);
Console.WriteLine("In Method - A.Something={0}", a.Something);
b = new B { Id = 33 };
}
To invoke the method with the appropriate arguments we will loop over the array returned by GetParameters
and populate our object[]
.
To determine if our parameter is an out
parameter we must look at two things: ParameterInfo.IsOut
and ParameterInfo.ParameterType.IsByRef
*. If both values are true
we will simply leave a space in the array. Otherwise we will use the ParameterType
property to query the dictionary for the appropriate instance (throwing an exception if we don't have an instance):
var dict = new Dictionary<Type, object>
{
[typeof(int)] = 5,
[typeof(string)] = "Hello",
[typeof(A)] = new A { Something = "World" }
};
MethodInfo method = typeof(Program).GetMethod(nameof(MyMethod));
// get the parameters and create the input array
var @params = method.GetParameters();
var methodArgs = new object[@params.Length];
// loop over the parameters
// see below for LINQ'ified version
for (var i = 0; i < @params.Length; i++)
{
var p = @params[i];
// if it's an output parameter, ignore its value
if (p.IsOut && p.ParameterType.IsByRef)
continue;
// get the value based on the parameter type or throw if not found
if (!dict.TryGetValue(p.ParameterType, out var arg))
throw new InvalidOperationException("Cannot find parameter of type " + p.ParameterType.Name);
methodArgs[i] = arg;
}
Finally we will invoke the method using the arguments array we have created. Once the method completes, we can access the output parameter via the appropriate value in the array
// Note: if Method is NOT a static method, we need to pass the instance as
// the first parameter. This demo uses a static method so we pass null
method.Invoke(null, methodArgs);
Console.WriteLine("In Main - B={0}", methodArgs[3]);
Running the above will print the following:
In Method - i=5
In Method - x=Hello
In Method - A.Something=World
In Main - B=[B] Id=33
You can "shorten" the loop to the following:
var methodArgs2 = method.GetParameters()
.Select(param =>
param.IsOut && param.ParameterType.IsByRef ? null :
dict.TryGetValue(param.ParameterType, out var pValue) ? pValue :
throw new InvalidOperationException($"Parameter of type {param.ParameterType.Name} was not found"))
.ToArray();
If you are absolutely certain that your dictionary will contain all of the appropriate types, we can make it simpler:
var methodArgs = method.GetParameters().Select(p => p.IsOut && p.ParameterType.IsByRef ? null : dict[p.ParameterType]).ToArray();
Note: if you statically know the type you can cast it back from object
.
B b = (B)methodArgs[3];
Unfortunately you will not be able to do this with the Type
object alone. If you don't know the type you could switch using pattern matching or is
/as
Note: you could also get the parameter's zero-based index (something you mention above) using the ParameterInfo.Position
property:
var index = p.Position;
methodArgs[index] = ... ;
* Testing only ParameterInfo.IsOut
is not sufficient. Compilers may insert the OutAttribute
in other cases (such as COM). An out
parameter will have both this attribute (which is what sets IsOut
) and will also have a by-ref type T&
. Similarly, ref
parameters will have the IsByRef
flag, but they will not have IsOut
. See this answer for more details.
Now, if you had ref
parameters you'd have to do things a bit differently. First of all ref
typically assumes that a value exists as a pre-condition, but VB doesn't necessarily require it (where out
does not exist). To check for parameters of this type you'd first have to check !p.IsOut && p.ParameterType.IsByRef
. Then, to get the instance from your dictionary you'll need to turn the ByRef type (T&
) into a normal one (T
) using GetElementType
:
if (!dict.TryGetValue(p.ParameterType.GetElementType(), out var pValue)) {
// if you know it's safe to pass null, then continue
// otherwise you may need to create a new object using Activator
}
Upvotes: 3