有没有办法从myFunc(new int {1,2,3}}中删除myFunc(1,2,3)?

向所有人提出问题C#向导。 我有一个方法,称之为myFunc,它需要可变长度/类型参数列表。 myFunc本身的参数签名是myFunc(params object[] args) ,我在列表上使用reflection(例如,想想这有点像printf)。

我想以myFunc(new int[] { 1, 2, 3 })区别对待myFunc(1, 2, 3) myFunc(new int[] { 1, 2, 3 }) 。 也就是说,在myFunc的主体内,我想枚举我的参数的类型,并希望最终得到{int,int,int}而不是int []。 现在我得到了后者:实际上,我无法区分这两种情况,它们都以int []的forms出现。

我希望前者会显示为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); } } } 

或者,如果使用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; } } 

两个方案的产出:

 First call Int32 Int32 Int32 Second call Int32[] 

现在给出上面的输出,不清楚你的问题究竟来自哪里……虽然你给了Foo("1", "2", "3") vs Foo(new string[] { "1", "2", "3" })然后这将是另一回事 – 因为string[]object[]兼容,但int[]不是。 如果这是给您带来问题的真实情况,那么请查看动态版本 – 这将在两种情况下都有效。

好的,所以让我们说我们放弃了另一个问题,你错误地认为这是一个编译器错误并且实际上解决了你的真实问题。

首先,让我们试着说出真正的问题。 这是我的镜头:


序言:

“可变参数”方法是一种采用未指定的超前参数数量的方法。

在C#中实现可变方法的标准方法是:

 void M(T1 t1, T2 t2, params P[] p) 

也就是说,零个或多个必需参数后跟一个标记为“params”的数组。

在调用这种方法时,该方法适用于其正常forms(没有参数)或其扩展forms (使用参数)。 也就是说,打电话给

 void M(params object[] x){} 

forms

 M(1, 2, 3) 

生成为

 M(new object[] { 1, 2, 3 }); 

因为它仅适用于其扩展forms。 但是一个电话

 M(new object[] { 4, 5, 6 }); 

生成为

 M(new object[] { 4, 5, 6 }); 

并不是

 M(new object[] { new object[] { 4, 5, 6 } }); 

因为它适用于其正常forms。

C#支持引用类型元素数组的不安全数组协方差。 也就是说,即使尝试将此类数组的第一个元素更改为非字符串,也可以将string[]隐式转换为object[] ,这将产生运行时错误。

问题是:

我希望打电话给表格:

 M(new string[] { "hello" }); 

并且此方法仅适用于扩展forms:

 M(new object[] { new string[] { "hello" }}); 

而不是正常的forms:

 M((object[])(new string[] { "hello" })); 

在C#中是否有办法实现可变方法,这些方法不会成为不安全数组协方差和优先以正常forms应用的方法的组合?


答案

是的,有办法,但你不会喜欢它。 如果您打算将单个数组传递给它,那么最好使该方法不可变。

Microsoft的C#实现支持一个未记录的扩展 ,它允许不使用params数组的C风格的可变方法。 此机制不适用于一般用途,仅包含在CLR团队和其他编写互操作库的人员中,以便他们可以编写桥接C#和期望C风格可变方法的语言之间的互操作代码。 我强烈建议您不要试图这样做。

这样做的机制涉及使用未记录的__arglist关键字。 基本草图是:

 public static void M(__arglist) { var argumentIterator = new ArgIterator(__arglist); object argument = TypedReference.ToObject(argumentIterator.GetNextArg()); 

您可以使用参数迭代器的方法遍历参数结构并获取所有参数。 并且您可以使用超级魔法类型的引用对象来获取参数的类型。 甚至可以使用这种技术将变量的引用作为参数传递,但我不建议这样做。

这种技术特别糟糕的是调用者需要说:

 M(__arglist(new string[] { "hello" })); 

在C#中坦率地看起来相当粗糙。 现在你明白为什么你最好完全放弃可变方法; 只是让调用者传递一个数组并完成它。

同样,我的建议是(1)在任何情况下你都不应该尝试使用C#语言的这些未记录的扩展,这些扩展旨在为CLR实现团队和互操作库作者提供便利,并且(2)你应该简单地放弃可变方法; 它们似乎不适合您的问题空间。 不要与工具作斗争; 选择一个不同的工具。

是的,您可以检查参数长度并检查参数类型,请参阅以下工作代码示例:

 class Program { static void Main(string[] args) { myFunc(1, 2, 3); myFunc(new int[] { 1, 2, 3 }); } static void myFunc(params object[] args) { if (args.Length == 1 && (args[0] is int[])) { // called using myFunc(new int[] { 1, 2, 3 }); } else { //called using myFunc(1, 2, 3), or other } } } 

你可以通过打破列表中的第一个元素并提供额外的重载来实现这样的事情,例如:

 class Foo { public int Sum() { // the trivial case return 0; } public int Sum(int i) { // the singleton case return i; } public int Sum(int i, params int[] others) { // eg Sum(1, 2, 3, 4) return i + Sum(others); } public int Sum(int[] items) { // eg Sum(new int[] { 1, 2, 3, 4 }); int i = 0; foreach(int q in items) i += q; return i; } } 

这在C#中是不可能的。 C#将在编译时通过第二次调用替换您的第一个调用。

一种可能性是创建一个没有params的重载并使其成为ref参数。 这可能没有意义。 如果你想根据输入改变行为,也许给第二个myFunc另一个名字。

更新

我现在明白你的问题。 你想要的是不可能的。 如果唯一的参数是可以解析为object[]任何东西,则无法区分它。

您需要一个替代解决方案,可能有一个由调用者创建的字典或数组来构建参数。

事实certificate存在一个真正的问题,它归结为C#进行类型推断的方式。 请参阅此其他主题的讨论