6

I am designing a custom attribute class.

public class MyAttr: Attribute
{
    public ValueRange ValRange { get; set; }
}

Then I am attempting to assign this attribute to a property in an adjoining class:

public class Foo
{
    [MyAttr(ValRange= new ValueRange())]
    public string Prop { get; set; }
}  

However, the compiler is complaining the following:

'ValRange' is not a valid named attribute argument because it is not a valid attribute parameter type

I also tried converting the ValueRange class to a struct in hopes that become a value type might solve the problem. Is there any way around this?

Matthew Cox
  • 13,566
  • 9
  • 54
  • 72

5 Answers5

20

Is there any way around this?

No.

For more details I refer you to section 17.1.3 of the C# 4 specification, which I reproduce here for your convenience:


The types of positional and named parameters for an attribute class are limited to the attribute parameter types, which are:

  • One of the following types: bool, byte, char, double, float, int, long, sbyte, short, string, uint, ulong, ushort.
  • The type object.
  • The type System.Type.
  • An enum type, provided it has public accessibility and the types in which it is nested (if any) also have public accessibility.
  • Single-dimensional arrays of the above types.

A constructor argument or public field which does not have one of these types, cannot be used as a positional or named parameter in an attribute specification.


Remember, the point of an attribute is to at compile time add information to the metadata associated with the entity upon which you've placed the attribute. That means that all the information associated with that attribute must have a well-defined, unambiguous way to serialize it into and out of metadata. By restricting the set of legal types to a small subset of all possible types we ensure that the compiler can always emit legal metadata that the consumer can understand.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    I remember the first time I ran up against this and thought, "Aww. :(" But then I thought about what the world would be if I actually _could_ have done it, and realized that this limitation makes a _ton_ of sense. – Greg D Apr 27 '11 at 20:10
  • I didn't find anything proving it but I figured this would be the case. Thank you for the resource. But based upon the documentation ... I could just store it is type Object then cast it when I need to use it in code elsewhere (which is very infrequently). Unfortunately there will be associated overhead but would that not be feasible? – Matthew Cox Apr 27 '11 at 20:22
  • 1
    @Matthew: No. The value you provide for the object field must be known fully at compile time as either a constant of one of the given types, null, a typeof expression, or a single-dimensional array where all the values in it are similarly "known at compile time". Again, I repeat the key point: the point of an attribute is to add information **to metadata at compile time**. The **compiler** has to have full knowledge of the information being added. Attributes are not code that runs when the program runs; they are extra metadata to be stuck into the compiled program. – Eric Lippert Apr 27 '11 at 21:14
  • ahh, I understand. That makes sense after rethinking it through. I just simply took the class and turned it into an attribute as well and that approach solved the problem. – Matthew Cox Apr 27 '11 at 21:59
  • 1
    "I just simply took the class and turned it into an attribute as well and that approach solved the problem." - For some definition of "solved"? – Greg D Apr 28 '11 at 13:06
2

Attribute parameter values need to be resolvable at compile time (i.e constants).

See Attribute Parameter Types on MSDN:

Values passed to attributes must be known to the compiler at compile time.

If you can create a ValueRange that is a constant, you can use it.

Oded
  • 489,969
  • 99
  • 883
  • 1,009
2

Is there any way around this?

Yes.

You can have your attribute use a Type property and then use types that implement a defined interface, for which the code that processes that attribute would have to assume, and as such also create an implicit, but hopefully documented, requirement to its clients:

public interface IValueRange {
  int Start { get; }
  int End { get; }
}
public class MyAttr : Attribute { 
  // The used type must implement IValueRange
  public Type ValueRangeType { get; set; } 
}

// ....

public class Foo { 

  class FooValueRange : IValueRange {
    public int Start { get { return 10; } }
    public int End { get { return 20; } }
  }
  [MyAttr(ValueRangeType = typeof(FooValueRange))]
  public string Prop { get; set; }

}

This is not unlike many classes in the System.ComponentModel namespace, like DesignerAttribute.

Jordão
  • 55,340
  • 13
  • 112
  • 144
1

Attribute parameters must be values of the following types (quoting the article):

  • Simple types (bool, byte, char, short, int, long, float, and double)
  • string
  • System.Type
  • enums
  • object (The argument to an attribute parameter of type object must be a constant value of one of the above types.)
  • One-dimensional arrays of any of the above types

Edit: Changed "compile-time constant" to "value", since types and arrays are not constants (thanks to the commenter who pointed this out (and subsequently deleted his comment for some reason...))

Aasmund Eldhuset
  • 37,289
  • 4
  • 68
  • 81
1

Attributes can only receive compile-time-constants as parameters (e.g. 3, "hello", typeof(MyClass), "path to a resource defining whatever non constant data you need").

The last example (passing a type) I gave may help you design a workaround (pass a type implementing an interface with the method you need).

Danny Varod
  • 17,324
  • 5
  • 69
  • 111