使用params object 构造函数的属性会产生不一致的编译器错误

我收到了错误

属性参数必须是属性参数类型的常量表达式,typeof表达式或数组创建表达式

请注意下面的截图:

在此处输入图像描述

请注意,如果我将DataRow属性与一个或三个参数一起使用,则不会出现编译错误。 但是如果我使用两个参数而第二个参数是一个字符串数组,那么我确实会遇到编译错误。

DataRowAttribute的签名是public DataRowAttribute (object data1);public DataRowAttribute (object data1, params object[] moreData);

第一个给我没问题,但第二个似乎变得困惑。

我认为params object[]可能会造成一些混乱。 也许它无法确定我的意思是[DataRow(new[] { 1 }, new[] { "1" })]还是[DataRow(new[] { 1 }, "1")]

为了解决这个问题,我尝试将第二个属性转换为object[DataRow(new[] { 1 }, (object)new[] { "1" })] ),但错误没有消失,它警告我认为演员是多余的。 我也尝试明确指定数组的类型,但这也没有帮助。

我可以添加第三个虚拟参数,即使null似乎解决了这个问题,但这只是一种解决方法。 这样做的正确方法是什么?

tldr:

正确的解决方法是告诉编译器不要使用展开的表单:

 [DataRow(new[] { 1 }, new object[] { new[] { "1" } })] 

过度分析:

迈克尔兰德尔的答案基本上是正确的。 让我们通过简化您的示例来深入研究:

 using System; [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public class MyAttribute : Attribute { public MyAttribute(params object[] x){} } public class Program { [MyAttribute()] [MyAttribute(new int[0])] [MyAttribute(new string[0])] // ERROR [MyAttribute(new object[0])] [MyAttribute(new string[0], new string[0])] public static void Main() { } } 

我们首先考虑非错误情况。

  [MyAttribute()] 

正常forms没有足够的参数。 构造函数适用于其扩展forms。 编译器编译它就像你写的那样:

  [MyAttribute(new object[0])] 

接下来,怎么样

  [MyAttribute(new int[0])] 

? 现在我们必须确定构造函数是否适用于其正常forms或扩展forms。 它不适用于普通forms,因为int[]不能转换为object[] 。 它适用于扩展forms,因此编译就像你写的一样

  [MyAttribute(new object[1] { new int[0] } )] 

那怎么样?

  [MyAttribute(new object[0])] 

构造函数适用于其正常forms和扩展forms。 在那种情况下,正常forms获胜。 编译器生成写入的调用。 它不会将对象数组包装在第二个对象数组中。

关于什么

  [MyAttribute(new string[0], new string[0])] 

? 普通forms的论据太多了。 使用扩展forms:

  [MyAttribute(new object[2] { new string[0], new string[0] })] 

这一切都应该是直截了当的。 然后出了什么问题:

  [MyAttribute(new string[0])] // ERROR 

? 那么,首先,它是否适用于正常或扩展forms? 显然它适用于扩展forms。 不太明显的是,它也适用于正常forms。 int[]不会隐式转换为object[]但是string[]会这样做! 这是一个不安全的协变数组引用转换 ,它在我的“最差C#特性”列表中名列前茅。

由于重载决策表明这适用于普通forms和扩展forms,因此正常forms获胜,并且编译就好像您已编写的那样

 [MyAttribute((object[]) new string[0] )] // ERROR 

我们来探讨一下。 如果我们修改上面的一些工作案例:

  [MyAttribute((object[])new object[0])] // SOMETIMES ERROR! [MyAttribute((object[])new object[1] { new int[0] } )] [MyAttribute((object[])new object[2] { new string[0], new string[0] })] 

现在所有这些都在早期版本的C#中失败并在当前版本中成功。

显然,编译器以前不允许在对象数组上进行转换,甚至不允许进行标识转换。 现在它允许身份转换,但不允许协变数组转换。

允许通过编译时常量值分析处理的强制转换; 你可以做

 [MyAttribute(new int[1] { (int) 100} )] 

如果你愿意,因为常量分析器会删除转换。 但属性分析器不知道如何处理object[]的意外转换,因此它会产生错误。

你提到的另一个案例怎么样? 这是有趣的!

 [MyAttribute((object)new string[0])] 

再说一次,让我们理解它。 这仅适用于其扩展forms,因此应该像编写时一样进行编译

 [MyAttribute(new object[1] { (object)new string[0] } )] 

但那是合法的 。 为了保持一致,要么这两种forms都应该是合法的,要么两者都应该是非法的 – 坦率地说,我并不关心这两种方式 – 但一方是合法的而另一方是不合法的,这是奇怪的。 考虑报告错误。 (如果这实际上是一个错误,那可能是我的错。很抱歉。)

它的长短是: 将params对象[]与数组参数混合是一种混淆的方法 。 尽量避免它。 如果您正在将数组传递给params object []方法,请以正常forms调用它。 创建一个new object[] { ... }并将参数自己放入数组中。

假设你的构造函数是

 public Foo(params object[] vals) { } 

然后我认为你正在遇到一些被忽视的非显而易见的编译器Dark Magic

例如,显然下面的内容可行

 [Foo(new object[] { "abc", "def" },new object[] { "abc", "def" })] [Foo(new string[] { "abc", "def" },new string[] { "abc", "def" })] 

这对我也有用

 [Foo(new [] { 2 }, new [] { "abc"})] [Foo(new [] { 1 }, new [] { "a"})] 

但事实并非如此

 [Foo(new [] { "a" })] [Foo(new [] { "aaa"})] [Foo(new string[] { "aaa" })] 

属性参数必须是属性参数类型的常量表达式,typeof表达式或数组创建表达式

我认为这里的关键信息是关键

具有params数组的方法可以以“正常”或“扩展”forms调用。 正常forms就好像没有“参数”。 扩展forms使用参数并将它们捆绑成一个自动生成的数组。 如果两种forms都适用,则普通forms胜过扩展forms。

举个例子

 PrintLength(new string[] {"hello"}); // normal form PrintLength("hello"); // expanded form, translated into normal form by compiler. 

当给出适用于两种forms的调用时,编译器总是在扩展forms上选择普通forms。

然而,我认为这会再次与object[]甚至属性相混淆。

我不会假装我确切地知道CLR正在做什么(而且还有更多合格的人可以回答)。 但是为了参考,请查看CLR SO向导 Eric Lippert的类似答案,以更详细地说明可能发生的事情

C#params object []奇怪的行为

为什么params表现得像这样?

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