Getting started with attributes in .NET– part II
In the previous post, we’ve started looking at how we can use attributes to improve the metadata of a type. Even though we’ve looked at some attributes and seen how the C# compiler reduces the required typing by allowing us to skip the Attribute suffix, I didn’t really got into details about how attributes are defined.
In practice, an attribute is always a class. CLR compatible attributes are represented by classes which derive, directly or indirectly, from the Attribute class. Whenever we apply an attribute to a type or member, the compiler needs to create an instance of that type. In fact, you’ve probably noticed that the syntax used for applying an attribute is similar to a constructor call (without the new operator). C# allows us to use a special syntax for setting up properties too. The next snippet starts by creating a new custom attribute and shows how you can initialize its properties in C#:
As you can see, we’ve started by passing the String used for initializing the private name field required by the constructor (since I didn’t specify a default constructor, there’s no way to create a new DumbAttribute instance without passing at least a string!). After that, I’ve initialized the MoreInfo read/write property by using a pair name/value. The docs use different names for identifying these different types of parameters:
- Positional parameters represent parameters passed to the constructor (notice that the order is important here!)
- Named parameters are always defined after positional parameters and allows us to initialize public fields or properties.
When we don’t need to pass any parameters to an attribute instantiation, then we can simply omit the the parameters like we did in the samples shown in one of the last posts:
In C#, there are several ways for us to apply several attributes to a type or member. We can wrap each attribute with its own square brackets ([ ]) or we can use a single pair of square brackets and separate each attribute with a comma. The next snippet shows both approaches:
Before ending, there’s still time for a couple of observations about custom attributes:
- constructor parameters, fields and properties are restricted to a small set of types: Boolean, Char, (S)Byte, (U)Int16, (U)Int32, (U)Int64, Single, Double, String, Type and Object.
- You can use single dimension arrays of the previous types (though you shouldn’t use them as positional parameters).
- Attribute usage in C# force us to use a compile-time constants. In practice, this means that Type “values” are initialized through the typeof operator. Object “values” can be initialized with any value of the list presented in 1 or any other constant expression (you can use null!). Once again, if the expression generates a value type value, it will get boxed at runtime.
These rules are needed due to the work performed by a compiler when it finds an attribute applied to a type or mem
ber. When that happens, the compiler needs emit information into the type’s or member metadata table so that it can create an instance of that attribute at runtime (each parameter is serialized before being stored and that is why we’re limited to those types and we can only resort to constant expressions).
In the next post, we’ll see how we can influence the elements to which attributes are applied. Stay tuned for more.