11

Is it possible to circumvent the following restriction:

Create a static readonly array in a class:

public class A
{
    public static readonly int[] Months = new int[] { 1, 2, 3};
}

Then pass it as a parameter to an attribute:

public class FooAttribute : Attribute
{
    public int[] Nums { get; set; }

    FooAttribute()
    {
    }
}

--- Let's say Box is a property of class A ---

[Foo(Nums = A.Months)]
public string Box { get; set; }

I know this won't compile and will result in this error:

"An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type".

Is it possible to go around this somehow to be able to use the static array ? I'm asking since this will be much more convenient maintenance-wise, since I have a lot of properties.

Thanks in advance.

Omri Aharon
  • 16,959
  • 5
  • 40
  • 58
  • 1
    should "readonly" be "const" conceptually? – David May 23 '13 at 09:43
  • 1
    Maybe, but they are [different](http://msdn.microsoft.com/en-us/library/acdd6hb7(v=vs.110).aspx): The readonly keyword is different from the const keyword. A const field can only be initialized at the declaration of the field. A readonly field can be initialized either at the declaration or in a constructor. Therefore, readonly fields can have different values depending on the constructor used. – bash.d May 23 '13 at 09:45

3 Answers3

12

Unfortunately this is not possible. The attributes (including the values of their arguments) are placed into the assembly metadata by the compiler so it has to be able to evaluate them at compile time (hence the restriction to constant expressions; the exception for array creation expressions was obviously made because otherwise you could not have array arguments at all).

In contrast, the code that actually initializes A.Months is only executed at runtime.

Jon
  • 428,835
  • 81
  • 738
  • 806
  • 1
    or in other words, attribute needs to be determined at compile time, and readonly vars are assigned at runtime? Is this understanding correct? – David May 23 '13 at 09:48
12

No, basically.

You could, however, subclass the attribute and use that, i.e.

class AwesomeFooAttribute : FooAttribute {
    public AwesomeFooAttribute() : FooAttribute(A.Months) {}
}

or:

class AwesomeFooAttribute : FooAttribute {
    public AwesomeFooAttribute() {
        Nums = A.Months;
    }
}

and decorate with [AwesomeFoo] instead. If you use reflection to look for FooAttribute, it will work as expected:

[AwesomeFoo]
static class Program
{
    static void Main()
    {
        var foo = (FooAttribute)Attribute.GetCustomAttribute(
            typeof(Program), typeof(FooAttribute));
        if (foo != null)
        {
            int[] nums = foo.Nums; // 1,2,3
        }
    }
}

You could perhaps nest this inside A, so you are decorating with:

[A.FooMonths]

or similar

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Hmmm... and I wonder: what happens if you reflect in a reflection-only context? – Jon May 23 '13 at 09:54
  • 1
    @Jon yeah, then you're scuppered. The metadata doesn't know anything at all about what happens inside. If you only have access to metadata, all you will be able to tell is "there is an `AwesomeFooAttribute`, which I can tell is itself a `FooAttribute`". Most people, however, aren't working in reflection-only contexts. As it happens, I *do* do a lot of that (via IKVM.Reflection, usually) - and I feel your pain :) – Marc Gravell May 23 '13 at 09:55
  • For a minute there I thought that somehow the code was equivalent and the compiler went out of its way to see that the field is initialized with a new array... which would theoretically be possible in this specific instance but is of course ridiculous as a feature because it wouldn't work in many cases. Thanks for clearing that up. – Jon May 23 '13 at 10:01
  • @Jon indeed, this is 99% runtime; but a useful trick since the majority of cases are going to be using runtime, not reflection-only – Marc Gravell May 23 '13 at 10:11
  • @MarcGravell: Thanks Marc, interesting solution, though I can't afford to have a new attribute and do that in the constructor :-( Will keep that in mind when the opportunity arises though! – Omri Aharon May 23 '13 at 10:23
  • @Seyana indeed, it doesn't fit every scenario (hence the "No, basically" ;p) – Marc Gravell May 23 '13 at 10:24
5

Short answer: No.

But you can refer to the int array by key:

public class A
{
    public static readonly Dictionary<int, int[]> NumsArrays 
              = new[]{{1, new[]{1,1,1}}, {2, new[]{2,2,2}}, {3, new[]{3,3,3}}};
    public const int Num1 = 1;
    public const int Num2 = 2;
    public const int Num3 = 3;
}

public class FooAttribute : Attribute
{
    public int NumsId { get; set; }

    FooAttribute()
    {
    }
}

[Foo(NumsID = A.Num3)]
public string Box { get; set; }

//Evaluation:
int id = (FooAttribute) Attribute.GetCustomAttribute(type, typeof (FooAttribute));
int[] result = A.NumsArrays[id];//result is {3,3,3}
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
Ahmed KRAIEM
  • 10,267
  • 4
  • 30
  • 33