Using attributes
By Ludwig Stuyck ([Link]@[Link])
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. Under-
standing 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 [Link];
using [Link];
namespace Attributes1
{
public class Person
{
public Person()
{
}
private string name = [Link];
public string Name
{
get { return name; }
Page 1 of 11
Ctg Technical Articles
set { name = value; }
}
private string location = [Link];
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)
{
[Link] = name;
[Link] = location;
[Link] = 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 System;
using [Link];
using [Link];
using [Link];
using [Link];
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>();
[Link](person1);
[Link](person2);
[Link](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(@"[Link]", [Link]))
{
[Link](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 [Link]:
default:
return number1+number2;
case [Link]:
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 [Link] code file:
using System;
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
using [Link];
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");
[Link](animalSoundsNode);
TreeNode humanSoundsNode = new TreeNode("Human sounds");
[Link](humanSoundsNode);
TreeNode machineSoundsNode = new TreeNode("Machine sounds");
[Link](machineSoundsNode);
// Add sounds for category animal sounds
TreeNode catNode = new TreeNode("Cat");
[Link] = new SoundDelegate(CatSound);
[Link](catNode);
TreeNode dogNode = new TreeNode("Dog");
[Link] = new SoundDelegate(DogSound);
[Link](dogNode);
[Link]();
}
public void CatSound()
{
[Link]("Miauw");
}
public void DogSound()
{
[Link]("Barf");
}
private void treeView_AfterSelect(object sender,
TreeViewEventArgs e)
{
if ([Link] != null)
{
SoundDelegate del = [Link] as SoundDelegate;
[Link]();
}
}
}
}
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 [Link];
using [Link];
namespace Attributes3
{
public class SoundAttribute : Attribute
{
}
}
Next, add two properties Category and Sound, and a constructor:
using System;
using [Link];
using [Link];
namespace Attributes3
{
public delegate void SoundDelegate();
[AttributeUsage([Link])]
public class SoundAttribute : Attribute
{
private string category = [Link];
public string Category
{
get { return category; }
set { category = value; }
}
private string sound = [Link];
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)
{
[Link] = category;
[Link] = sound;
}
}
}
We apply the AttributeUsage attribute to our custom attribute to make sure
that it can only be used on methods.
In the [Link] code file we will define the methods that produce sounds, and
apply our custom SoundAttribute to them:
[Sound("Animal sounds", "Cat")]
public void CatSound()
{
[Link]("Miauw");
}
[Sound("Animal sounds", "Dog")]
public void DogSound()
{
[Link]("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 = [Link]().GetMethods();
foreach (MethodInfo method in methods)
{
object[] attributes = [Link](
typeof(SoundAttribute), false);
if ([Link] == 1)
{
SoundAttribute soundAttribute =
(SoundAttribute)attributes[0];
if ()
{
Using attributes – By Ludwig Stuyck Page 9 of 11
Ctg Technical Articles
[Link]([Link],
[Link]);
}
TreeNode soundNode = new TreeNode([Link]);
[Link] = method;
[Link][[Link]].[Link](
soundNode);
}
}
[Link]();
}
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 ([Link] != null)
{
MethodInfo method = [Link] as MethodInfo;
[Link](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()
{
[Link]("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