We’ve already covered a lot of ground on the basics series…but guess what? There’s still a lot more to cover Today, we’ll start looking at how we can improve our .NET code with attributes. With attributes, we can add custom metadata to our types (and/or a type’s members). After adding extra information to a type (or to a type’s member), you can then query it at runtime through reflection so that you can influence the way some piece of code is executed. Make no mistake about it: we’re talking about some very interesting stuff here! If you don’t believe me, then take a quick look around and you’ll notice that several .NET technologies rely heavily on it (for instance, it’s used in Windows Forms, WCF, WPF, Siverlight, serialization – oh damn, serialization is also an interesting topic for future posts, don’t you think? – you name it!).
In .NET, anyone can reuse one of the predefined attributes or just create new one for a specific scenario. Since attributes are targeted at the CLR, then all compatible compilers must understand them, interpret them and add the required entries to a type’s metadata. Before going on, it’s important to understand that most attributes have only one purpose: defining the metadata that will be associated with a type or member. And that’s it. To drive this point home, let’s take a look at the following code:
public class Student {
private String _name;
public String Name {
get { return _name; }
set { _name = value; }
}
}
The first thing you should notice in the previous snippet is that we apply an attribute to a type (or member) through the use of square brackets. In the previous snippet, we’re using the SerializableAttribute to say that Student type instances can be serialized. In C#, you can apply attributes to the assembly, module, types, fields, methods, method return values, properties, events and generic type parameters. Once again, from the previous example, it’s easy to understand that, by default, an attribute is applied to the associated type or member. There are, however, scenarios where you need to use a prefix to make it clear to what you’re applying an attribute to. The next snippet shows how to apply an attribute to an assembly:
Besides assembly, you can also use the module prefix if you intend to annotate the module with a specific attribute and the return prefix if you want to make sure that an attribute is applied to the return value of a method. If you’re targeting the compiler generated field or methods of an event declaration, then you need to use the field and method prefixes:
public class Student {
private String _name;
public String Name {
get { return _name; }
set { _name = value; }
}
[method: MyDummy]
[field: MyDummy]
public event EventHandler SomethingHappened;
}
MyDummy is a custom attribute (we’ll return to this topic in the future). Interestingly, these last two prefixes are optional when you’re targeting a method or a field:
public void DoIt() { }
Finally, there are also the property and param prefixes which can be used to indicate that an attribute is being used to annotate a property or a parameter:
public String Name {
//applied to method: no need to use method:
[MyDummy]
get { return _name; }
set { _name = value; }
}
public void PrintIt([/*param:*/MyDummy]String info) { }
If you’re just starting, then you’ve probably noticed that the compiler allows us to cut the Attribute suffix from the attribute name. This is only possible because the C# compiler team thought that it would be a good idea to reduce the amount of typing and to improve the readability.
And that’s it for now. In the next post we’ll keep looking at attributes. Stay tuned for more.