繁体   English   中英

如何使用 C# 中的反射调用具有多个输入\输出参数的 function 不知道它们的顺序?

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

我有一个类型实例t

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

和两个Out In

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

我假设调用tAction不需要其他 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.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;
}

最后,我们将使用我们创建的 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM