如何使用 C# 中的反射调用具有多个 in\out 参数但不知道其顺序的函数?
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)
和两个词典 In
和 Out
- 里面有一对
{Type, instance}
。
我假设调用 t
的 Action
不需要其他参数。
那么如何使用反射调用具有多个输入和输出的函数/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.IsOut
和 ParameterInfo.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)插入 OutAttribute
。 out
参数将同时具有此属性(设置 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
}
我有一个类型实例t
:
- 带有一个函数,其签名我只知道像这样的一般结构
public void Action(T1in ps, ..., TNin ps, out T1out,..., out TKout)
和两个词典 In
和 Out
- 里面有一对
{Type, instance}
。
我假设调用 t
的 Action
不需要其他参数。
那么如何使用反射调用具有多个输入和输出的函数/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.IsOut
和 ParameterInfo.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)插入 OutAttribute
。 out
参数将同时具有此属性(设置 IsOut
的属性)并且还将具有引用类型 T&
。类似地,ref
参数将具有 IsByRef
标志,但它们将 而不是 具有 IsOut
。有关详细信息,请参阅 this answer。
现在,如果您有 ref
个参数,您的操作方式将有所不同。首先,ref
通常假设一个值作为先决条件存在,但 VB 不一定需要它(其中 out
不存在)。要检查这种类型的参数,您首先必须检查 !p.IsOut && p.ParameterType.IsByRef
。然后,要从字典中获取实例,您需要使用 GetElementType
:
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
}