[英]How to call a function with multiple in\out params not knowing their order using Reflection in C#?
我有一个类型实例t
:
public void Action(T1in ps, ..., TNin ps, out T1out,..., out TKout)
这样的一般结构和两个Out
In
{Type, instance}
。 我假设调用t
的Action
不需要其他 arguments 。
那么如何使用 Reflection/ Dynamitey调用具有多个输入和输出的 function 知道它们的类型但不知道它们的顺序?
当您通过反射调用方法时,您传递一个包含所有参数的object[]
。 对于out
参数,您将数组中的相应值保留为null
。 方法完成后,您可以从数组中相应的 position 检索值。
为了证明这一点,让我们假设以下两个类:
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
}
我们将调用以下 static 方法,其中包括三个输入参数和一个out
参数(类型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 };
}
要使用适当的 arguments 调用该方法,我们将遍历GetParameters
返回的数组并填充我们的object[]
。
要确定我们的参数是否为out
参数,我们必须查看两件事: ParameterInfo.IsOut
和ParameterInfo.ParameterType.IsByRef
*。 如果两个值都为true
,我们将在数组中留下一个空格。 否则,我们将使用ParameterType
属性在字典中查询适当的实例(如果我们没有实例则抛出异常):
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;
}
最后,我们将使用我们创建的 arguments 数组调用该方法。 方法完成后,我们可以通过数组中的适当值访问 output 参数
// 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]);
运行上述将打印以下内容:
在方法中 - i=5
在方法中 - x=Hello
在方法中 - A.Something=World
在主要 - B=[B] Id=33
您可以将循环“缩短”为以下内容:
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();
如果您绝对确定您的字典将包含所有适当的类型,我们可以使其更简单:
var methodArgs = method.GetParameters().Select(p => p.IsOut && p.ParameterType.IsByRef ? null : dict[p.ParameterType]).ToArray();
注意:如果您静态知道类型,则可以从object
将其转换回。
B b = (B)methodArgs[3];
不幸的是,您将无法单独使用 object Type
执行此操作。 如果您不知道可以使用模式匹配或is
/ as
切换的类型
注意:您还可以使用ParameterInfo.Position
属性获取参数的从零开始的索引(上面提到的内容):
var index = p.Position;
methodArgs[index] = ... ;
*仅测试ParameterInfo.IsOut
是不够的。 编译器可能会在其他情况下(例如 COM)插入OutAttribute
。 out
参数将具有此属性(这是设置IsOut
的属性),并且还将具有按引用类型T&
。 同样, ref
参数将具有IsByRef
标志,但它们不会具有IsOut
。 有关更多详细信息,请参阅此答案。
现在,如果你有ref
参数,你必须做一些不同的事情。 首先ref
通常假定一个值作为前提条件存在,但 VB 不一定需要它(其中out
不存在)。 要检查这种类型的参数,您首先必须检查.p.IsOut && p.ParameterType.IsByRef
。 然后,要从字典中获取实例,您需要使用GetElementType
将 ByRef 类型 ( T&
) 转换为普通类型 ( T
):
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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.