带有 params object[] 构造函数的属性给出了不一致的编译器错误

Attribute with params object[] constructor gives inconsistent compiler errors

我遇到了错误

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

注意下面的截图:

请注意,如果我将 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 似乎也能解决这个问题,但这只是一种解决方法。正确的做法是什么?

假设你的构造函数是

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" })]

An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type

我认为这里的关键信息是

A method with a params array may be called in either "normal" or "expanded" form. Normal form is as if there was no "params". Expanded form takes the params and bundles them up into an array that is automatically generated. If both forms are applicable then normal form wins over expanded form.

举个例子

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

当给定一个适用于两种形式的调用时,编译器总是选择普通形式而不是扩展形式。

但是我认为 object[] 甚至属性会变得更加混乱。

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

Why does params behave like this?

Is there a way to distingish myFunc(1, 2, 3) from myFunc(new int[] { 1, 2, 3 })?

tldr:

正确的解决方法是告诉编译器不要使用扩展形式:

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

过度分析:

Michael Randall 的回答基本正确。让我们通过简化您的示例来深入研究:

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()]

范式的参数不足。构造函数以其扩展形式适用。编译器编译它就像你写的一样:

    [MyAttribute(new object[0])]

接下来,

    [MyAttribute(new int[0])]

?现在我们必须决定构造函数是否适用于其正常形式或扩展形式。它不适用于正常形式,因为 int[] 不能转换为 object[]。它适用于扩展形式,因此编译时就像您编写的那样

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

现在呢

    [MyAttribute(new object[0])]

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

怎么样

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

?范式的参数太多了。使用扩展形式:

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

这应该很简单。那么有什么问题:

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

?那么,首先,它适用于正常形式还是扩展形式?显然它适用于扩展形式。不太明显的是它也适用于正常形式。 int[] 不会隐式转换为 object[]string[] 会!这是一个 不安全的协变数组引用转换 ,它在我的 "worst C# feature" 列表中名列前茅。

由于重载决议表明这适用于普通形式和扩展形式,所以普通形式胜出,并且它的编译就像您编写的那样

[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# 中都失败了,但在当前版本中成功了。

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

可以通过编译时间常数值分析处理的转换是允许的;你可以做到

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

如果你愿意,因为常量分析器删除了该转换。但是属性分析器不知道如何处理意外转换为 object[],因此它给出了一个错误。

你提到的另一个案例呢? 这个很有趣!

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

再次,让我们推理一下。这仅适用于其扩展形式,因此应该像您编写的那样进行编译

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

但是这是合法的。为了保持一致,这两种形式要么都是合法的,要么都是非法的——坦率地说,我真的不在乎这两种形式——但奇怪的是,一种是合法的,另一种不是。考虑报告错误。 (如果这实际上是一个错误,那可能是我的错。对此深表歉意。)

总而言之:将参数对象[]与数组参数混合使用会导致混淆。尽量避免它。如果您处于将数组传递给 params object[] 方法的情况,请以其正常形式调用它。做一个new object[] { ... },自己把参数放到数组里。