Using attributes

By Ludwig Stuyck (Ludwig.Stuyck@ctg.com)
Last update: March 5, 2007

A

ttributes allow us to add metadata to our code at compile time – also known as declarative programming. There are predefined attributes in the .NET framework, but you can create your own custom attributes too. Understanding attributes allows you to create designs where functionality is loosely coupled.

What is an attribute?
An attribute is a piece of additional declarative information that is specified for a declaration. This information can then be used at runtime or design time (by application development tools, for example). They are applied in front of a declaration between square brackets “[“ and “]”. Once associated with a declaration, the attribute can be queried at run time and used for multiple purposes.

A first example
Let’s start with a little example: create a console application and call it Attributes1. Add a class, call it Person and implement it as follows:
using System; using System.Collections.Generic; using System.Text; namespace Attributes1 { public class Person { public Person() { } private string name = string.Empty; public string Name { get { return name; }

Page 1 of 11

Ctg Technical Articles
set { name = value; } } private string location = string.Empty; public string Location { get { return location; } set { location = value; } } private int age = 0; public int Age { get { return age; } set { age = value; } } public Person(string name, string location, int age) { this.name = name; this.location = location; this.age = age; } } }

In the Main method we will create a generic collection of Person objects, and write some code to save the collection to a file:
using using using using using System; System.Collections.Generic; System.Text; System.IO; System.Runtime.Serialization.Formatters.Binary;

namespace Attributes1 { class Program { static void Main(string[] args) { // Create some persons Person person1 = new Person("Ludwig Stuyck", "Zaventem", 33); Person person2 = new Person("Leen Rans", "Zaventem", 25); Person person3 = new Person("Paulien Rans", "Rotselaar", 2); // Create a collection of persons List<Person> persons = new List<Person>(); persons.Add(person1); persons.Add(person2); persons.Add(person3);

Using attributes – By Ludwig Stuyck

Page 2 of 11

Ctg Technical Articles
// Save the collection to a file BinaryFormatter formatter = new BinaryFormatter(); using (FileStream fileStream = new FileStream(@"persons.dat", FileMode.Create)) { formatter.Serialize(fileStream, persons); } } } }

If you now run the application, you will get an exception, telling you that the type Person is not marked as serializable:

We solve this by decorating the class Person with the Serializable attribute:
[Serializable] public class Person { public Person() { } … }

Run the application again, and now it creates the file successfully. What we have done is telling the compiler that the Person class can be serialized, by applying the predefined SerializableAttribute to it. I say predefined, because it’s already in the .NET framework. You can create your own attributes, which we will discuss later. For now, just remember that an attribute allows you to add additional metadata for a given type (class, interface, structure...), member (property, method…) or assembly at compile time. This attribute can then be read by other software by analyzing your assembly; and this process is called reflection.

Using attributes – By Ludwig Stuyck

Page 3 of 11

Ctg Technical Articles

So in the case of our application, when the Serialize method is called, it first checks whether the Serializable attribute is applied to the Person object, and if it isn’t, an exception is thrown.

Another example
Imagine that you are deploying a class library and that it contains the following method to calculate the sum of two integers and returns the result:
public int CalculateSum(int number1, int number2) { return number1 + number2; }

A few months later you decide to replace this method by another one that is the preferred one, for example:
public enum CalculationType {Sum, Subtraction} public int Calculate(CalculationType type, int number1, int number2) { switch (type) { case CalculationType.Sum: default: return number1+number2; case CalculationType.Subtraction: return number1-number2; } }

It’s not recommended to just remove the old method CalculateSum because that would break other code. But what you can do is to mark it as obsolete, by decorating it with the Obsolete attribute, and passing some message as a parameter:
[Obsolete("This method is obsolete, use Calculate instead.")] public int CalculateSum(int number1, int number2) { return number1 + number2; }

If you do this, and someone is using your class library and the old CalculateSum method, then he will get a compiler warning:

Using attributes – By Ludwig Stuyck

Page 4 of 11

Ctg Technical Articles

In this case, the compiler sees the Obsolete attribute applied to your method and displays the warning.

Building custom attributes
In some cases attributes can be used as an elegant solution to a problem. I will give you an example where attributes can be used to make a better design. Let’s say that we want to create a tree view, with a number of sound categories and each category contains a list of things that produce a sound:

If you click on the Cat item, the sound is shown in a message box:

Using attributes – By Ludwig Stuyck

Page 5 of 11

Ctg Technical Articles

First approach
Create a new windows application and call it Attributes2. Drop a TreeView control on the form and call it treeView. Now, add the following in the Form1.cs code file:
using using using using using using using System; System.Collections.Generic; System.ComponentModel; System.Data; System.Drawing; System.Text; System.Windows.Forms;

namespace Attributes2 { public partial class Form1 : Form { public delegate void SoundDelegate(); public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e)

Using attributes – By Ludwig Stuyck

Page 6 of 11

Ctg Technical Articles
{ // Add sound categories TreeNode animalSoundsNode = new TreeNode("Animal sounds"); treeView.Nodes.Add(animalSoundsNode); TreeNode humanSoundsNode = new TreeNode("Human sounds"); treeView.Nodes.Add(humanSoundsNode); TreeNode machineSoundsNode = new TreeNode("Machine sounds"); treeView.Nodes.Add(machineSoundsNode); // Add sounds for category animal sounds TreeNode catNode = new TreeNode("Cat"); catNode.Tag = new SoundDelegate(CatSound); animalSoundsNode.Nodes.Add(catNode); TreeNode dogNode = new TreeNode("Dog"); dogNode.Tag = new SoundDelegate(DogSound); animalSoundsNode.Nodes.Add(dogNode); treeView.ExpandAll(); } public void CatSound() { MessageBox.Show("Miauw"); } public void DogSound() { MessageBox.Show("Barf"); } private void treeView_AfterSelect(object sender, TreeViewEventArgs e) { if (e.Node.Tag != null) { SoundDelegate del = e.Node.Tag as SoundDelegate; del.Invoke(); } } } }

As you see, in the Load method three sound categories are added to the tree view (“Animal sounds”, “Human sounds” and “Machine sounds”). Then two child tree nodes (“Cat” and “Dog”) are added to the “Animal sounds” node, and the Tag property of the child tree node is assigned to the method that produces the correct sound message box (via a delegate). Finally, in the AfterSelect event of the tree view, we just invoke the delegate that is attached to the Tag property of the selected tree node. Run the code to see if it works. Yes it does. But… adding new categories or sounds to the tree view Using attributes – By Ludwig Stuyck Page 7 of 11

Ctg Technical Articles comes down to adding nodes to the tree view… a lot of nodes… imagine how complex the code can become when hundreds of nodes are added?

Second approach: using attributes
We will solve this in another way, using a custom attribute. Create a new windows application, call it Attributes3 and again, drop a TreeView control on the form and call it treeView. First, we will create our custom attribute: add a new class to the project, call it SoundAttribute, and make it inherit from the Attribute class:
using System; using System.Collections.Generic; using System.Text; namespace Attributes3 { public class SoundAttribute : Attribute { } }

Next, add two properties Category and Sound, and a constructor:
using System; using System.Collections.Generic; using System.Text; namespace Attributes3 { public delegate void SoundDelegate(); [AttributeUsage(AttributeTargets.Method)] public class SoundAttribute : Attribute { private string category = string.Empty; public string Category { get { return category; } set { category = value; } } private string sound = string.Empty; public string Sound { get { return sound; } set { sound = value; } }

Using attributes – By Ludwig Stuyck

Page 8 of 11

Ctg Technical Articles
public SoundAttribute(string category, string sound) { this.category = category; this.sound = sound; } } }

We apply the AttributeUsage attribute to our custom attribute to make sure that it can only be used on methods. In the Form1.cs code file we will define the methods that produce sounds, and apply our custom SoundAttribute to them:
[Sound("Animal sounds", "Cat")] public void CatSound() { MessageBox.Show("Miauw"); } [Sound("Animal sounds", "Dog")] public void DogSound() { MessageBox.Show("Barf"); }

And now the only thing that still needs to be done is to construct the tree view with categories and sounds. The idea is to write code that will find every method to which the SoundAttribute is applied to, and fill the tree view with the found methods. This has to be done once, and afterwards when we add new methods and apply the SoundAttribute to them, they will automatically be added to the tree view. Finding all methods to which the SoundAttribute is applied is done by reflection:
private void Form1_Load(object sender, EventArgs e) { MethodInfo[] methods = this.GetType().GetMethods(); foreach (MethodInfo method in methods) { object[] attributes = method.GetCustomAttributes( typeof(SoundAttribute), false); if (attributes.Length == 1) { SoundAttribute soundAttribute = (SoundAttribute)attributes[0]; if (!treeView.Nodes.ContainsKey(soundAttribute.Category)) {

Using attributes – By Ludwig Stuyck

Page 9 of 11

Ctg Technical Articles
treeView.Nodes.Add(soundAttribute.Category, soundAttribute.Category); } TreeNode soundNode = new TreeNode(soundAttribute.Sound); soundNode.Tag = method; treeView.Nodes[soundAttribute.Category].Nodes.Add( soundNode); } } treeView.ExpandAll(); }

If you now run the application, the tree view is populated:

The last thing we need to do is to implement the AfterSelect event:
private void treeView_AfterSelect(object sender, TreeViewEventArgs e) { if (e.Node.Tag != null) { MethodInfo method = e.Node.Tag as MethodInfo; method.Invoke(this, null); } }

Run the application again, and click on a node. The message box is displayed with the corresponding sound.

Using attributes – By Ludwig Stuyck

Page 10 of 11

Ctg Technical Articles So now when you have to add new sounds, all you need to do is to add a new method and decorate it with a SoundAttribute. Try it: add a new method, for example:
[Sound("Human sounds", "Hallo")] public void HalloSound() { MessageBox.Show("Hallo"); }

If you run the application, this human sound will be in the tree view, and when you click on it the message box will appear:

Attribute targets
Attributes can be applied to various types of declarations, the AttributeTargets enumeration lists them all: all, Assembly, Class, Constructor, Delegate, Enum, Event, Field, GenericParameter, Interface, Method, Module, Parameter, Property, ReturnValue and Struct.

Using attributes – By Ludwig Stuyck

Page 11 of 11