123

Is it possible to create an attribute that can be initialized with a variable number of arguments?

For example:

[MyCustomAttribute(new int[3,4,5])]  // this doesn't work
public MyClass ...
Crono
  • 10,211
  • 6
  • 43
  • 75

8 Answers8

201

Attributes will take an array. Though if you control the attribute, you can also use params instead (which is nicer to consumers, IMO):

class MyCustomAttribute : Attribute {
    public int[] Values { get; set; }

    public MyCustomAttribute(params int[] values) {
       this.Values = values;
    }
}

[MyCustomAttribute(3, 4, 5)]
class MyClass { }

Your syntax for array creation just happens to be off:

class MyCustomAttribute : Attribute {
    public int[] Values { get; set; }

    public MyCustomAttribute(int[] values) {
        this.Values = values;
    }
}

[MyCustomAttribute(new int[] { 3, 4, 5 })]
class MyClass { }
Mark Brackett
  • 84,552
  • 17
  • 108
  • 152
41

You can do it, but it isn't CLS compliant:

[assembly: CLSCompliant(true)]

class Foo : Attribute
{
    public Foo(string[] vals) { }
}
[Foo(new string[] {"abc","def"})]
static void Bar() {}

Shows:

Warning 1   Arrays as attribute arguments is not CLS-compliant

For regular reflection usage, it may be preferable to have multiple attributes, i.e.

[Foo("abc"), Foo("def")]

However, this won't work with TypeDescriptor/PropertyDescriptor, where only a single instance of any attribute is supported (either the first or last wins, I can't recall which).

Ben Collins
  • 20,538
  • 18
  • 127
  • 187
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • 4
    note: multiple attributes requires a AttributeUsage attribute on your attribute. http://stackoverflow.com/questions/553540/how-to-create-duplicate-allowed-attribute – russau Mar 12 '12 at 03:36
28

Try declaring the constructor like this:

public class MyCustomAttribute : Attribute
{
    public MyCustomAttribute(params int[] t)
    {
    }
}

Then you can use it like:

[MyCustomAttribute(3, 4, 5)]

Scott Dorman
  • 42,236
  • 12
  • 79
  • 110
14

That should be okay. From the spec, section 17.2:

An expression E is an attribute-argument-expression if all of the following statements are true:

  • The type of E is an attribute parameter type (§17.1.3).
  • At compile-time, the value of E can be resolved to one of the following:
    • A constant value.
    • A System.Type object.
    • A one-dimensional array of attribute-argument-expressions.

Here's an example:

using System;

[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public class SampleAttribute : Attribute
{
    public SampleAttribute(int[] foo)
    {
    }
}

[Sample(new int[]{1, 3, 5})]
class Test
{
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
7

Yes, but you need to initialize the array that you are passing in. Here is an example from a row test in our unit tests that tests a variable number of command line options;

[Row( new[] { "-l", "/port:13102", "-lfsw" } )]
public void MyTest( string[] args ) { //... }
Rob Prouse
  • 22,161
  • 4
  • 69
  • 89
2

You can do that. Another example could be:

class MyAttribute: Attribute
{
    public MyAttribute(params object[] args)
    {
    }
}

[MyAttribute("hello", 2, 3.14f)]
class Program
{
    static void Main(string[] args)
    {
    }
}
Alan
  • 6,501
  • 1
  • 28
  • 24
2

To piggy back on Marc Gravell's answer, yes you can define an attribute with array parameters but applying an attribute with an array parameter is not CLS-compliant. However just defining an attribute with an array property is perfectly CLS-compliant.

What made me realize this was that Json.NET, a CLS-compliant library, has an attribute class JsonPropertyAttribute with a property named ItemConverterParameters that's an array of objects.

TBrink
  • 21
  • 4
0

I use maybe a bit stupid workaround using this trick:

public class CLParam : Attribute
{
    /// <summary>
    /// Command line parameter
    /// </summary>
    public string Names { get; set; }
}

and then splitting the Names into string[]:

var names = loadAtt.Names.Split(',');

I allows me to use attribute like this:

class CLContext
{
    [CLParam(Names = "selectscene,ss")]
    public List<string> SelectScene { get; set; }

But of course for ints you would need to parse texts, so maybe a bit slow...

Michal Pokluda
  • 390
  • 4
  • 11