Skip to content

Unclear documentation of supported attribute array types #1299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
SeeSharpr opened this issue Mar 20, 2025 · 11 comments
Open

Unclear documentation of supported attribute array types #1299

SeeSharpr opened this issue Mar 20, 2025 · 11 comments

Comments

@SeeSharpr
Copy link

SeeSharpr commented Mar 20, 2025

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

@KalleOlaviNiemitalo
Copy link
Contributor

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

using System;

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

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

@jnm2
Copy link
Contributor

jnm2 commented Mar 20, 2025

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

@KalleOlaviNiemitalo
Copy link
Contributor

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.

@jnm2
Copy link
Contributor

jnm2 commented Mar 20, 2025

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

@SeeSharpr
Copy link
Author

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

@KalleOlaviNiemitalo
Copy link
Contributor

KalleOlaviNiemitalo commented Mar 21, 2025

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
Copy link
Contributor

Previous discussion in #268.

@SeeSharpr
Copy link
Author

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[].

@KalleOlaviNiemitalo
Copy link
Contributor

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

@BillWagner
Copy link
Member

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.)

@KalleOlaviNiemitalo
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants