如何使用 C# 中的反射调用具有多个 in/out 参数的函数,这些参数不知道它们的顺序?



我有一个类型实例t

  • 具有其签名的功能,我只知道像这样的一般结构public void Action(T1in ps, ..., TNin ps, out T1out,..., out TKout)

和两个词典InOut

  • 里面有成对的{Type, instance}

我假设不需要其他参数来调用tAction

那么如何使用反射/发电机调用具有多个输入和输出的函数,知道它们的类型而不是它们的顺序呢?

通过反射调用方法时,将传递一个包含所有参数的object[]。对于out参数,将数组中的相应值保留为null。方法完成后,您可以从数组中的相应位置检索值。

为了演示这一点,让我们假设以下两个类:

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
}

我们将调用以下静态方法,其中包括三个输入参数和一个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 };
}

要使用适当的参数调用该方法,我们将遍历GetParameters返回的数组并填充我们的object[]

要确定我们的参数是否是out参数,我们必须查看两件事:ParameterInfo.IsOutParameterInfo.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;
}

最后,我们将使用我们创建的参数数组调用该方法。方法完成后,我们可以通过数组中的相应值访问输出参数

// 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=你好

在方法 - A.某物=世界

在主 - 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];

遗憾的是,您将无法仅使用Type对象执行此操作。如果您不知道类型,可以使用模式匹配或is/as进行切换

注意:您还可以使用ParameterInfo.Position属性获取参数的从零开始的索引(上面提到的(:

var index = p.Position;
methodArgs[index] = ... ;

*测试ParameterInfo.IsOut是不够的。编译器可以在其他情况下插入OutAttribute(如 COM(。out参数将同时具有此属性(这是设置IsOut(,并且还将具有 by-ref 类型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
} 

最新更新