向所有人提出问题C#向导.我有一个方法,称之为myFunc,它需要可变长度/类型参数列表.myFunc本身的参数签名是myFunc(params object[] args),我在列表上使用反射(例如,想想这有点像printf).
我想要myFunc(1, 2, 3)区别对待myFunc(new int[] { 1, 2, 3 }).也就是说,在myFunc的主体内,我想枚举我的参数的类型,并希望最终得到{int,int,int}而不是int [].现在我得到后者:实际上,我无法区分这两种情况,它们都以int []的形式出现.
我希望前者显示为obs [].长度= 3,obs [0] = 1等.
而且我曾预计后者会显示为obs [].长度= 1,其中obs [0] = {int [3]}
这可以做到,还是我问不可能?
那么这样做会:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("First call");
Foo(1, 2, 3);
Console.WriteLine("Second call");
Foo(new int[] { 1, 2, 3 });
}
static void Foo(params object[] values)
{
foreach (object x in values)
{
Console.WriteLine(x.GetType().Name);
}
}
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您使用,DynamicObject您可以使用动态类型来实现类似的结果:
using System;
using System.Dynamic;
class Program
{
static void Main(string[] args)
{
dynamic d = new ArgumentDumper();
Console.WriteLine("First call");
d.Foo(1, 2, 3);
Console.WriteLine("Second call");
d.Bar(new int[] { 1, 2, 3 });
}
}
class ArgumentDumper : DynamicObject
{
public override bool TryInvokeMember
(InvokeMemberBinder binder,
Object[] args,
out Object result)
{
result = null;
foreach (object x in args)
{
Console.WriteLine(x.GetType().Name);
}
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
两个方案的产出:
First call
Int32
Int32
Int32
Second call
Int32[]
Run Code Online (Sandbox Code Playgroud)
现在给出上面的输出,不清楚你的问题究竟来自哪里......虽然如果你给了Foo("1", "2", "3")vs Foo(new string[] { "1", "2", "3" })那么那将是另一回事 - 因为string[]兼容object[],但int[]不是.如果这是给您带来问题的真实情况,那么请查看动态版本 - 这将在两种情况下都有效.
好吧,那么让我们说我们放弃了另一个问题,你错误地认为这是一个编译器错误并且实际上解决了你的真实问题.
首先,让我们试着说出真正的问题.这是我的镜头:
"可变参数"方法是采用未指定的提前数量的参数的方法.
在C#中实现可变方法的标准方法是:
void M(T1 t1, T2 t2, params P[] p)
Run Code Online (Sandbox Code Playgroud)
也就是说,零个或多个必需参数后跟一个标记为"params"的数组.
在调用这种方法时,该方法适用于其正常形式(没有参数)或其扩展形式(使用参数).也就是说,打电话给
void M(params object[] x){}
Run Code Online (Sandbox Code Playgroud)
形式
M(1, 2, 3)
Run Code Online (Sandbox Code Playgroud)
生成为
M(new object[] { 1, 2, 3 });
Run Code Online (Sandbox Code Playgroud)
因为它仅适用于其扩展形式.但是打个电话
M(new object[] { 4, 5, 6 });
Run Code Online (Sandbox Code Playgroud)
生成为
M(new object[] { 4, 5, 6 });
Run Code Online (Sandbox Code Playgroud)
并不是
M(new object[] { new object[] { 4, 5, 6 } });
Run Code Online (Sandbox Code Playgroud)
因为它适用于其正常形式.
C#支持引用类型元素数组上的不安全数组协方差.也就是说,即使尝试将这样的数组的第一个元素更改为非字符串也会产生运行时错误,string[]也可以隐式转换为object[]a.
我希望打电话给表格:
M(new string[] { "hello" });
Run Code Online (Sandbox Code Playgroud)
并且此方法仅适用于扩展形式:
M(new object[] { new string[] { "hello" }});
Run Code Online (Sandbox Code Playgroud)
而不是正常的形式:
M((object[])(new string[] { "hello" }));
Run Code Online (Sandbox Code Playgroud)
在C#中是否有办法实现可变方法,这些方法不会成为不安全数组协方差和优先以正常形式应用的方法的组合?
是的,有办法,但你不会喜欢它.如果您打算将单个数组传递给它,那么最好使该方法不可变.
Microsoft的C#实现支持一个未记录的扩展,它允许不使用params数组的C风格的可变方法.此机制不适用于一般用途,仅包含在CLR团队和其他编写互操作库的其他人中,以便他们可以编写桥接C#和期望C样式可变方法的语言之间的互操作代码.我强烈建议对试图这样做你自己.
这样做的机制涉及使用未记录的__arglist关键字.基本草图是:
public static void M(__arglist)
{
var argumentIterator = new ArgIterator(__arglist);
object argument = TypedReference.ToObject(argumentIterator.GetNextArg());
Run Code Online (Sandbox Code Playgroud)
您可以使用参数迭代器的方法遍历参数结构并获取所有参数.并且您可以使用超级魔法类型的引用对象来获取参数的类型.甚至可以使用这种技术将变量的引用作为参数传递,但我不建议这样做.
这种技术特别糟糕的是调用者需要说:
M(__arglist(new string[] { "hello" }));
Run Code Online (Sandbox Code Playgroud)
在C#中坦率地看起来相当粗糙.现在你明白为什么你最好完全放弃可变方法; 只是让调用者传递一个数组并完成它.
同样,我的建议是(1)在任何情况下都不应该尝试使用C#语言的这些未记录的扩展,这些扩展旨在为CLR实现团队和互操作库作者提供便利,并且(2)您应该简单地放弃可变方法; 它们似乎不适合您的问题空间.不要与工具作斗争; 选择一个不同的工具.
这在 C# 中是不可能的。C# 将在编译时用第二次调用替换第一次调用。
一种可能性是创建一个不带重载params并将其作为ref参数。这可能没有意义。如果您想根据输入更改行为,可以给第二个 myFunc 另一个名称。
更新
我现在明白你的问题了。你想要的东西是不可能的。如果唯一的论点是任何可以解决的问题,object[]那么就不可能与此区分开来。
您需要一个替代解决方案,也许有一个由调用者创建的字典或数组来构建参数。