csharpstandard icon indicating copy to clipboard operation
csharpstandard copied to clipboard

Unclear documentation of supported attribute array types

Open SeeSharpr opened this issue 11 months ago • 11 comments

Type of issue

Spec incorrect

Description

I wanted to provide some feedback regarding section 22.2.4 of the C# specification, which outlines the supported types for attribute parameters. While the documentation mentions "single-dimensional arrays of the above types" as valid attribute parameter types, it doesn't explicitly clarify that arrays of reference types like string are treated differently by the compiler in certain scenarios.

For example, while new int[] {1, 2} works fine as an attribute argument, new string[] {"foo", "bar"} produces the error "An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type." This behavior stems from how the compiler handles arrays of reference types versus primitive types in constant expressions.

It would be helpful if the documentation explicitly stated that single-dimensional arrays of reference types (such as string, object, and System.Type) are subject to limitations when used as attribute arguments, even if they are technically valid attribute parameter types.

Thank you for considering this feedback to improve clarity in the C# documentation!

UPDATE: Apologies for missing an important fact. This reproduces when using the Online data attribute on xUnit. It does takes the parameters as an object[].

Page URL

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/language-specification/attributes?source=docs#2224-attribute-parameter-types

Content source URL

https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/attributes.md

SeeSharpr avatar Mar 20 '25 19:03 SeeSharpr

new string[] {"foo", "bar"} works OK here:

using System;

class UhAttribute : Attribute
{
    public UhAttribute(string[] p) {}
}

[Uh(new string[] {"foo", "bar"})]
class N{}

KalleOlaviNiemitalo avatar Mar 20 '25 21:03 KalleOlaviNiemitalo

I was able to repro when the argument becomes an element of a params object[] parameter.

using System;

class UhAttribute : Attribute
{
    public UhAttribute(params object[] p) {}
}

[Uh(new string[] {"foo", "bar"})]
class N{}

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

(SharpLab)

As a workaround, you can write:

[Uh(new object[] { new string[] {"foo", "bar"} })]

jnm2 avatar Mar 20 '25 21:03 jnm2

However, array type covariance doesn't work in attribute arguments. If the parameter type is object[], then the compiler does not accept a string[] or Type[] argument, even though there are implicit conversions from those types to object[]. For C# implementations that target the CLR, this restriction is caused by ECMA-335 II.23.3 Custom attributes, which does not allow the actual type of the array argument to be encoded when the parameter already has array type. But I did not find this specified in the C# standard.

KalleOlaviNiemitalo avatar Mar 20 '25 21:03 KalleOlaviNiemitalo

Ah, I see, the params part was not relevant. So the interesting thing is that new[] will lead you astray:

using System;

class UhAttribute : Attribute
{
    public UhAttribute(object[] p) {}
}

// Infers string[], which is not allowed
[Uh(new[] {"foo", "bar"})]
class N{}

Instead, you can use collection expressions (if on langversion 12+) which builds an object[] via target-typing:

// Infers object[], which is allowed
[Uh(["foo", "bar"])]

Or, explicitly write the type of the array:

// Explicitly specifies object[], which is allowed
[Uh(new object[] { "foo", "bar" })]

jnm2 avatar Mar 20 '25 21:03 jnm2

Updated the description. I observe the faulty behavior on the InlineData attribute on xUnit. new int[]{...} works but not new string[]{...}

SeeSharpr avatar Mar 21 '25 00:03 SeeSharpr

In this case


using System;

class UhAttribute : Attribute
{
    public UhAttribute(params object[] p) {}
}

[Uh(new int[] {1,2})]
class N{}

there is no conversion from int[] to object[] so the compiler treats it as meaning [Uh(new object[] { new int[] {1,2} })], which does not need array covariance and is allowed.

KalleOlaviNiemitalo avatar Mar 21 '25 05:03 KalleOlaviNiemitalo

Note that in xUnit the argument of InlineData expects object[] because they map each entry in the object array to a single parameter in the test case.

When someone passes a new string[]{"foo","bar,"} this is not supposed to mean 2 entries in the object [], just one that has the type string[] in the test function.

We are also supposed to be able to pass in additional arguments before and after the string[].

For example: [InlineData(new string[]{"foo"}, new string[]{"bar"}, false)] public void Test(string[] x, string[] y, bool z)

It works fine with int[], but fails with string[].

SeeSharpr avatar Mar 21 '25 16:03 SeeSharpr

We are also supposed to be able to pass in additional arguments before and after the string[].

For example: [InlineData(new string[]{"foo"}, new string[]{"bar"}, false)] public void Test(string[] x, string[] y, bool z)

AFAICT this works already. SharpLab

using System;

public class InlineDataAttribute:Attribute {
    public InlineDataAttribute(params object?[] p){}
}

public class C{
    [InlineData(new string[]{"foo"}, new string[]{"bar"}, false)]
    public void Test(string[] x, string[] y, bool z){}
}

KalleOlaviNiemitalo avatar Mar 21 '25 16:03 KalleOlaviNiemitalo

adding @jaredpar

I checked older versions of the spec, and this language hasn't changed since v1. I didn't check the earlier compiler for its behavior.

Jared, should this be a spec change, or is this is bug in roslyn? (Happy to make the spec change since this is long standing behavior.)

BillWagner avatar Mar 21 '25 17:03 BillWagner

If this were a bug in Roslyn, then how could it be fixed? Given public FooAttribute(params object[] p) and [Foo(new string[] {"x"})]:

  • Translate to [Foo(new object[] {"x"})].
    • Con: does not make the string[] that the developer intended.
  • Translate to [Foo(new object[] { new string[] {"x"} })].
    • Con: not what happens in overload resolution in a method call, thus not expected by developers.
  • Encode the string[].
    • Con: a lot of work. Needs an ECMA-335 augment, a runtime change, and perhaps changes in obfuscators or other tools that read metadata.
    • The current attribute blob always starts with the number 1 as a signature. It would be possible to assign a different number for a new encoding that supports array covariance.

KalleOlaviNiemitalo avatar Mar 21 '25 18:03 KalleOlaviNiemitalo