6

As shown here, attribute constructors are not called until you reflect to get the attribute values. However, as you may also know, you can only pass compile-time constant values to attribute constructors. Why is this? I think many people would much prefer to do something like this:

[MyAttribute(new MyClass(foo, bar, baz, jQuery)]

than passing a string (causing stringly typed code too!) with those values, turned into strings, and then relying on Regex to try and get the value instead of just using the actual value, and instead of using compile-time warnings/errors depending on exceptions that might be thrown somewhere that has nothing to do with the class except that a method that it called uses some attributes that were typed wrong.

What limitation caused this?

Community
  • 1
  • 1
It'sNotALie.
  • 22,289
  • 12
  • 68
  • 103
  • Why do you think the timing of the realization of attributes has anything to do with what type of data can be contained in them? The bottom line is attribute data needs to be discoverable statically, which is not possible for arbitrary C# syntax and types. – Kirk Woll Jun 01 '13 at 19:16
  • @KirkWoll Attributes are constructed only when you reflect into them. You would work out the value when you reflect into it. – It'sNotALie. Jun 01 '13 at 20:02
  • 1
    Where are do the symbols foo, bar, etc supposed to come from and what are they? Variables or constants or something else? – Journey Jun 01 '13 at 20:34
  • @Journey They could come from something like this: `[MyAttribute(new MyType(3, "string constant", new MyOtherType(4, null, "test")))]` and so on. – It'sNotALie. Jun 01 '13 at 21:50

4 Answers4

13

Attributes are part of metadata. You need to be able to reflect on metadata in an assembly without running code in that assembly.

Imagine for example that you are writing a compiler that needs to read attributes from an assembly in order to compile some source code. Do you really want the code in the referenced assembly to be loaded and executed? Do you want to put a requirement on compiler writers that they write compilers that can run arbitrary code in referenced assemblies during the compilation? Code that might crash, or go into infinite loops, or contact databases that the developer doesn't have permission to talk to? The number of awful scenarios is huge and we eliminate all of them by requiring that attributes be dead simple.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
4

The issue is with the constructor arguments. They need to come from somewhere, they are not supplied by code that consumes the attribute. They must be supplied by the Reflection plumbing when it creates the attribute object by calling its constructor. For which it needs the constructor argument values.

This starts at compile time with the compiler parsing the attribute and recording the constructor arguments. It stores those argument values in the assembly metadata in a binary format. At issue then is that the runtime needs a highly standardized way to deserialize those values, one that preferably doesn't depend on any of the .NET classes that you'd normally use the de/serialize data. Because there's no guarantee that such classes are actually available at runtime, they won't be in a very trimmed version of .NET like the Micro Framework.

Even something as common as binary serialization with the BinaryFormatter class is troublesome, note how it requires the [Serializable] attribute on the class to allow it to do its job. Versioning would also be an enormous problem, clearly such a serializer class could never change for the risk of breaking attributes in old assemblies.

This is a rock and a hard place, solved by the CLS designers by heavily restricting the allowed types for an attribute constructor. They didn't leave much, just the simple values types, string, a simple one-dimensional array of them and Type. Never a problem deserializing them since their binary representation is simple and unambiguous. Quite a restriction but attributes can still be pretty expressive. The ultimate fallback is to use a string and decode that string in the constructor at runtime. Creating an object of MyClass isn't an issue, you can do so in the attribute constructor. You'll have to encode the arguments that this constructor needs however as properties of the attribute.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Couldn't you serialize type names as strings and then do something like this: [MyAttribute(new MyType(3, "string constant", new MyOtherType(4, null, "test")))] ? – It'sNotALie. Jun 02 '13 at 16:07
  • Not so sure what you are proposing. But you'll have to stop using *new* in attribute declarations, code is not supported either. Only literals. As stated, if you need to create an object then you need to do so in the attribute class constructor. With fairly high odds that you are on the wrong track because the code that reads the attribute back won't know beans about the class you create. Do focus a bit on how you are going to *use* the attribute, that's most often overlooked. – Hans Passant Jun 02 '13 at 16:17
  • What I'm saying is why isn't that sort of thing possible? – It'sNotALie. Jun 02 '13 at 17:06
  • I wrote that up in my answer. Any part that's unclear about it? – Hans Passant Jun 02 '13 at 17:33
  • What I'm saying is, as you can serialize strings and types, couldn't they do something like this: `(attribute starter)(typeof(MyType))(some delimiter)(contents [ints, strings, types, arrays, etc, basically whatever's allowed allowed])(some other delimiter)(end of attribute)`? – It'sNotALie. Jun 02 '13 at 18:30
  • Your point gets murkier every time you restate it. I just have no idea what you are proposing. This is otherwise very non-constructive and a waste of time, the .NET framework has hard rules for attributes that can't be altered. If you want to tinker with CLS basics then take a look at Mono. If you want to write code for .NET that you'll have to deal with what it supports, no workaround for that. – Hans Passant Jun 02 '13 at 18:39
0

The probably most correct answer as to why you can only use constants for attributes is because the C#/BCL design team did not judge supporting anything else important enough to be added (i.e. not worth the effort).

When you build, the C# compiler will instantiate the attributes you have placed in your code and serialize them, so that they can be stored in the generated assembly. It was probably more important to ensure that attributes can be retrieved quickly and reliably than it was to support more complex scenarios.

Also, code that fails because some attribute property value is wrong is much easier to debug than some framework-internal deserialization error. Consider what would happen if the class definition for MyClass was defined in an external assembly - you compile and embed one version, then update the class definition for MyClass and run your application: boom!

On the other hand, it's seriously frustrating that DateTime instances are not constants.

Morten Mertner
  • 9,414
  • 4
  • 39
  • 56
  • 1
    I agree that it is frustrating that DateTime has no easy way to make an attribute. There is a `DecimalConstantAttribute`, so it seems reasonable to make a `DateTimeConstantAttribute`, but there is no such thing. You can of course work around the limitation by turning the date into a string and storing that, or by storing a long containing a tick count, or three ints containing the year / month / day, and so on. – Eric Lippert Jun 03 '13 at 16:47
-1

What limitation caused this?

The reason it isn't possible to do what you describe is probably not caused by any limitation, but it's purely a language design decision. Basically, when designing the language they said "this should be possible but not this". If they really wanted this to be possible, the "limitations" would have been dealt with and this would be possible. I don't know the specific reasoning behind this decision though.

/.../ passing a string (causing stringly typed code too!) with those values, turned into strings, and then relying on Regex to try and get the value instead of just using the actual value /.../

I have been in similar situations. I sometimes wanted to use attributes with lambda expressions to implement something in a functional way. But after all, c# is not a functional language, and if I wrote the code in a non-functional way I haven't had the need for such attributes.

In short, I think like this: If I want to develop this in a functional way, I should use a functional language like f#. Now I use c# and I do it in a non-functional way, and then I don't need such attributes.

Perhaps you should simply reconsider your design and not use the attributes like you currently do.

UPDATE 1:

I claimed c# is not a functional language, but that is a subjective view and there is no rigourous definition of "Functional Language". I agree with the Adam Wright, "/.../ As such, I wouldn't class C# as functional in general discussion - it is, at best, multi-paradigm, with some functional flavour." at Why is C# a functional programmming language?

UPDATE 2: I found this post by Jon Skeet: https://stackoverflow.com/a/294259/1105687 It regards not allowing generic attribute types, but the reasoning could be similar in this case:

Answer from Eric Lippert (paraphrased): no particular reason, except to avoid complexity in both the language and compiler for a use case which doesn't add much value.

Community
  • 1
  • 1
lightbricko
  • 2,649
  • 15
  • 21
  • C# is a multi-paradigm language, including functional idioms. Furthermore, while lambdas could in principle be stored in an attribute (because code *is* static), most other values could not be stored in an attribute. How would you statically determine (without *running* any code) the value of an attribute such as `[MyAttribute(DateTime.Now)]`. Such dynamic values are *complete* nonsense for the concept of attributes. – Kirk Woll Jun 01 '13 at 19:32
  • Kirk Woll: Regarding functional: C# has added features to accommodate functional-style programming, but I still don't regard it a functional language. I have edited my answer to make it clear that this view is subjective. Regarding lambdas, I don't think it is possible: http://stackoverflow.com/questions/16809294/lambda-expression-in-attribute-constructor (I'm not sure what you mean with "could in principle be"). Regarding "How would you statically determine" and regarding "Such dynamic values are complete nonsense": You can't and yes it is! – lightbricko Jun 01 '13 at 20:03