I am getting the error
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
Notice the screenshot below:
Notice that if I use the DataRow attribute with one or three parameters, I don't get a compile error. But if I use two parameters and the second parameter is an array of strings, then I do get a compile error.
The signatures for DataRowAttribute are public DataRowAttribute (object data1);
and public DataRowAttribute (object data1, params object[] moreData);
The first one gives me no problem, but the second one seems to be getting confused.
I considered that maybe the params object[]
could be causing some confusion.
Maybe it couldn't determine whether I meant [DataRow(new[] { 1 }, new[] { "1" })]
or [DataRow(new[] { 1 }, "1")]
To resolve that, I tried to cast the second attribute to object
([DataRow(new[] { 1 }, (object)new[] { "1" })]
), but the error didn't go away and it warned me that the cast was redundant. I also tried specifying the types of the array explicitly, but that also did not help.
I could just add a third dummy parameter, even null seems to fix this, but that's just a workaround. What's the correct way to do this?
tldr:
The correct workaround is to tell the compiler to not use the expanded form:
Excessive analysis:
The answer of Michael Randall is basically correct. Let's dig in by simplifying your example:
Let's first consider the non error cases.
There are not enough arguments for the normal form. The constructor is applicable in its expanded form. The compiler compiles this as though you had written:
Next, what about
? Now we must decide if the constructor is applicable in its normal or expanded form. It is not applicable in normal form because
int[]
is not convertible toobject[]
. It is applicable in expanded form, so this is compiled as though you'd writtenNow what about
The constructor is applicable in both its normal and expanded form. In that circumstance the normal form wins. The compiler generates the call as written. It does NOT wrap the object array in a second object array.
What about
? There are too many arguments for the normal form. The expanded form is used:
That should all be straightforward. What then is wrong with:
? Well, first, is it applicable in normal or expanded form? Plainly it is applicable in expanded form. What is not so obvious is that it is also applicable in normal form.
int[]
does not implicitly convert toobject[]
butstring[]
does! This is an unsafe covariant array reference conversion, and it tops my list for "worst C# feature".Since overload resolution says that this is applicable in both normal and expanded form, normal form wins, and this is compiled as though you'd written
Let's explore that. If we modify some of our working cases above:
All of these now fail in earlier versions of C# and succeed in the current version.
Apparently the compiler previously allowed no conversion, not even an identity conversion, on the object array. Now it allows identity conversions, but not covariant array conversions.
Casts that can be handled by the compile time constant value analysis are allowed; you can do
if you like, because that conversion is removed by the constant analyzer. But the attribute analyzer has no clue what to do with an unexpected cast to
object[]
, so it gives an error.What about the other case you mention? This is the interesting one!
Again, let's reason it through. That's applicable only in its expanded form, so this should be compiled as though you'd written
But that is legal. To be consistent, either both these forms should be legal, or both should be illegal -- frankly, I don't really care either way -- but it is bizarre that one is legal and the other isn't. Consider reporting a bug. (If this is in fact a bug it is probably my fault. Sorry about that.)
The long and the short of it is: mixing params object[] with array arguments is a recipe for confusion. Try to avoid it. If you are in a situation where you are passing arrays to a params object[] method, call it in its normal form. Make a
new object[] { ... }
and put the arguments into the array yourself.Assuming your constructor is
Then i think you are running up against some overlooked and non obvious compiler Dark Magic.
For example, obviously the below will work
This also works for me
However this does not
I think the key take-home peice of information here is
As an example
When given a call that is applicable in both forms, the compiler always chooses the normal form over the expanded form.
However i think this gets even messier again with
object[]
and even attributes.I'm not going to pretend i know exactly what the CLR is doing (and there are many more qualified people that may answer). However for reference, take a look at the CLR SO wizard Eric Lippert's similar answers for a more detailed illumination of what might be going on
C# params object[] strange behavior
Why does params behave like this?
Is there a way to distingish myFunc(1, 2, 3) from myFunc(new int[] { 1, 2, 3 })?