C#params明显的编译器bug(C#5.0)

这是我认为昨天解决的一个post的后续内容。 昨天我在以下情况下遇到了我的代码问题:

using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication3 { class Program { class Bar { int v; public Bar(int v) { this.v = v; } public override string ToString() { return v.ToString(); } } static void Main(string[] args) { Foo(1, 2, 3); Foo(new int[] { 1, 2, 3 }); Foo(new Bar(1), new Bar(2), new Bar(3)); Foo(new Bar[] { new Bar(1), new Bar(2), new Bar(3) }); System.Threading.Thread.Sleep(20000); } static void Foo(params object[] objs) { Console.WriteLine("New call to Foo: "); foreach(object o in objs) Console.WriteLine("Type = " + o.GetType() + ", value = "+o.ToString()); } } } 

如果你运行它,你可以看到上次调用Foo时出现问题。 参数是向量的事实是“丢失”的。

那么….有谁知道如何报告C#编译器错误? 或者这会被视为reflection错误?

(真是令人宽慰:我很沮丧地认为我浪费了时间来处理我自己的错误。实际上它毕竟是一个C#错误,而且我得到了certificate!我们经常看到实际的C#编译器错误这几天?不常见……)

希望这两个调用function相同 – params参数是被调用方法中的数组。 Jon Skeet在上一个问题中的示例有效,因为int的数组与对象数组不协变(因此被视为new Object[] { new Int[] {1,2,3} } ),但在此示例中FooBars数组与对象数组协变,因此您的参数将扩展为objs参数。

所有事物的维基百科涵盖了这个确切的案例: 协方差和逆变(计算机科学)

抱歉,但我确信这不是编译器错误。

编辑:

你可以实现你想要的:

 Foo(new Object[] { new Bar[] { new Bar(1), new Bar(2), new Bar(3) } }); 

NEW EDIT(其他作者):

或者只是使用:

 Foo((Object)new Bar[] { new Bar(1), new Bar(2), new Bar(3) }); 

C#4.0规范非常清楚这一切是如何发挥作用的。 7.5.3.1表示如果具有params的函数可以以正常forms(忽略params关键字)或扩展forms(使用params关键字)应用,则普通forms获胜。

假设Foo被声明为Foo(params object[] args) ,则调用Foo(new Foobar[] {new Foobar(1), new Foobar(2), new Foobar(3)) }); 适用于正常forms,因为Foobar[]可隐式转换为object[] (6.1.6第5条)。 因此,使用常规forms并忽略扩展forms。

(我假设C#5.0没有改变这部分语言。)

请参阅C#规范的7.5.3.1节:

7.5.3.1适用的function成员

当满足以下所有条件时,函数成员被称为关于参数列表A的适用函数成员:

  • A中的每个参数对应于§7.5.1.1中描述的函数成员声明中的参数,并且无参数对应的任何参数都是可选参数。
  • 对于A中的每个参数,参数的参数传递模式(即value,ref或out)与相应参数的参数传递模式相同,并且
    • 对于值参数或参数数组,从参数到相应参数的类型存在隐式转换(第6.1节),或者
    • [……关于refout参数的一些不相关的材料…]

对于包含参数数组的函数成员,如果函数成员适用于上述规则,则称其适用于其正常forms。 如果包含参数数组的函数成员不适用于其正常forms,则函数成员可能适用于其扩展forms[。]

因为您传递的数组可以隐式转换为object[] ,并且因为重载解析比“扩展”forms更喜欢“普通”forms,所以您观察到的行为符合规范,并且没有错误。

除了Chris Shain描述的解决方法之外,您还可以将Bar从类更改为结构; 这使得数组不再可以隐式转换为object[] ,因此您将获得所需的行为。

我相信你错了Ken那是因为int []不是object []类型,所以编译器假定int []只是传递给方法的参数之一。

这是如何做:

 new Foobar[] { } is object[]; // true new int[] { } is object[]; // false 

更新:您可以使方法通用,让编译器知道struct / object类型传递为params:

 void Foo(params T[] objs) { foreach (T o in objs) Console.WriteLine(o.GetType()); } 

所以我建议最好的答案是我是对的,这确实是一个编译器错误。 虽然我清楚地看到了这一点,但您的解释忽略了“params”关键字。 事实上,要以这种方式使用协方差,必须忽略params关键字,就好像它不重要一样。 我假设没有合理的解释来编译代码这种方式,Params作为Foo类型签名上的关键字出现:要调用你的解释,你需要说服我myClass []应该与object []类型匹配但是,鉴于params构造,我们甚至不应该问这个问题。 实际上,您对此的想法越多,它就越清楚它实际上是一个真正的C#5.0编译器错误:编译器忽略了应用params关键字。 语言规范实际上根本不需要任何改变。 我应该得到某种奇怪的徽章,imho!