如何使用 C# 中的反射调用具有多个 in\out 参数但不知道其顺序的函数?

How to call a function with multiple in\out params not knowing their order using Reflection in C#?

我有一个类型实例t:

和两个词典 InOut

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

那么如何使用反射调用具有多个输入和输出的函数/Dynamitey知道它们的类型但不知道它们的顺序?

当您通过反射调用方法时,您传递了一个包含所有参数的 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,我们将简单地在数组中留下一个 space。否则我们将使用 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]);

运行 以上将打印以下内容:

In Method - i=5

In Method - x=Hello

In Method - A.Something=World

In Main - 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 属性:[=54= 获取参数的从零开始的索引(您在上面提到的内容) ]

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

* 仅测试 ParameterInfo.IsOut 是不够的。编译器可能会在其他情况下(例如 COM)插入 OutAttributeout 参数将同时具有此属性(设置 IsOut 的属性)并且还将具有引用类型 T&。类似地,ref 参数将具有 IsByRef 标志,但它们将 而不是 具有 IsOut。有关详细信息,请参阅 this answer

现在,如果您有 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
}