Attribute with constructor params object [] gives inconsistent compiler errors

I get an error

The attribute argument must be a constant expression, a typeof expression, or an array creation expression type attribute attribute

Check out the screenshot below:

enter image description here

Please note that if I use the DataRow attribute with one or three parameters, I do not get a compilation error. But if I use two parameters, and the second parameter is an array of strings, then I get a compilation error.

Signatures for DataRowAttribute : public DataRowAttribute (object data1); and public DataRowAttribute (object data1, params object[] moreData);

The first one does not give me problems, but the second one seems to get confused.

I thought that perhaps the params object[] could cause some confusion. Perhaps he could not determine if I had the value [DataRow(new[] { 1 }, new[] { "1" })] or [DataRow(new[] { 1 }, "1")]

To solve this problem, I tried to apply the second attribute to object ( [DataRow(new[] { 1 }, (object)new[] { "1" })] ), but the error did not disappear, and he warned me that the cast was redundant. I also tried to explicitly specify array types, but that also did not help.

I could just add the third parameter of the dummy element, even null seems to fix it, but this is just a workaround. What is the right way to do this?

+8
c # .net-attributes
source share
2 answers

TL; DR:

The correct workaround is to tell the compiler not to use the extended form:

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

Excessive analysis:

Michael Randall's answer is basically correct. Let me slip in to simplify your example:

 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() { } } 

First, consider the cases of errors.

  [MyAttribute()] 

Not enough arguments for normal form. The constructor is applicable in extended form. The compiler compiles this as if you wrote:

  [MyAttribute(new object[0])] 

Next how about

  [MyAttribute(new int[0])] 

? Now we have to decide if the constructor is applicable in its normal or extended form. This is not applicable in normal form, because int[] not converted to object[] . It is applicable in extended form, so it compiled as if you wrote

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

Now how about

  [MyAttribute(new object[0])] 

The constructor is applicable in both its normal and expanded form. In this case, the normal form wins. The compiler generates the call as written. It does not transfer an array of objects to a second array of objects.

What about

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

? There are too many arguments for a normal form. The extended form is used:

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

Everything should be simple. What's wrong with it:

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

? Well, firstly, is it applicable in the usual or expanded form? Simply put, it is applicable in expanded form. What is not so obvious is that it is also applicable in normal form. int[] implicitly converted to object[] , but string[] does! This is an unsafe covariant conversion of array references, and it matches my list of "the worst C # function".

Since overload resolution says that it is applicable in both normal and advanced forms, the normal form wins, and it compiled as if you wrote

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

Let me examine this. If we change some of our work cases above:

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

All of them now do not work in earlier versions of C # and succeed in the current version.

Apparently, the compiler previously did not allow conversion, even conversion of identifiers, into an array of objects. Now it allows you to convert identifiers, but not covariant transformations of arrays.

Effects that can be processed by analyzing compile-time values โ€‹โ€‹are allowed; You can do

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

if you like it because this conversion is removed by a constant parser. But the attribute analyzer does not know what to do with an unexpected click on object[] , so it gives an error.

What about the other case you mentioned? It is interesting!

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

Again, let me reason. This only applies in its expanded form, so it should be compiled as if you wrote

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

But that is legal . To be consistent, both of these forms must be legal, or both must be illegal - to be honest, I don't care, anyway - but it is strange that one is legal and the other is not. Review the error message. (If this is actually a mistake, this is probably my mistake. Sorry.)

Long and short: mixing params [] with array arguments is a recipe for confusion . Try to avoid this. If you are in a situation where you are passing arrays to the params object [] method, call it in its usual form. Make a new object[] { ... } and put the arguments into the array yourself.

+11
source share

Assuming your constructor

 public Foo(params object[] vals) { } 

Then I think that you are faced with some kind of missing and unobvious compiler Dark Magic.

For example, it is obvious that below will work

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

It also works for me.

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

However it is not

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

The attribute argument must be a constant expression, typeof expression, or an array creation expression type attribute attribute

I think the key hack of information here is

A method with params array can be called either โ€œnormalโ€ or โ€œExtended form. Normal form as if there were noโ€œ parameters. โ€The extended form takes parameters and binds them to an array that is automatically generated. If both forms are used, then the normal form wins in extended form.

As an example

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

When invoked, which is applicable in both forms, the compiler always selects the normal form from the extended form.

However, I think this again becomes more messy with object[] and even attributes.

I'm not going to pretend to know exactly what the CLR does (and there are many qualified people who can answer). However, for reference, look at the CLR SO Eric Lippert wizards for similar answers for more details on what might happen.

C # params object [] weird behavior

Why params behave like this:

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

+5
source share

All Articles