Table of Contents
Introduction
In this article I hope to show you what attributes are, how to use existing
attributes, and how to create your own attributes to use in your own projects.
What this article assumes you know or have
- You can read C# code
- You have some version of the .NET framework installed, my demo program
uses the final release so you'll have to recompile if you have an earlier
version.
What are attributes
Essentially attributes are a means of decorating your code with various
properties at compile time. This can be passive, such as marking a
class as Serializable
with the SerializableAttribute
, or it can take a more active
role, such as the MarshalAsAttribute
which tells the runtime how to pass data
between managed and unmanaged code.
In the former case when you try serializing an object, the runtime checks to
see if the object has the SerializableAttribute applied to it. In this
case the attribute is nothing more than a flag to let the runtime know that the
classes author gave the OK for serialization (since this could expose some
sensitive data to someone who should see it).
In the latter case a more active role is taken. The MarshalAsAttribute
when applied to a field in a struct will tell the runtime what type of data the
.NET type should be formatted as when it is sent to unmanaged code, and what
type it should convert the return value from when it comes back from the unmanaged code.
void MyMethod([MarshalAs(LPStr)] string s);
The above method marshals the string as an LPStr (zero terminated ANSI
string), the string is modified by the runtime so that it can be used by the
function MyMethod
.
All attributes inherit from System.Attribute
and are classes just like 90% of
the framework. This also means that what you can do with a class you can
do with an attribute; given an instance of an attribute you can get its
underlying type. You can also create attributes at runtime with the
Reflection.Emit
classes and bind them to classes you have built with the Emit
package.
How to use attributes
Simple Attributes
In C# attributes are placed before the item that they will be "decorating"
and they are put inside square braces [].
[Serializable()]
public class MySerializableClass
....
Like C#, VB.NET places attributes before the item that will be decorated,
except VB.NET uses angle brackets <>.
<Serializable()>
Public Class MySerializableClass
....
Notice that even though the real name for the attribute is
SerializableAttribute
we didn't write Attribute? The only time that you
can leave off the Attribute portion of the name is when you are applying the
Attribute to a class
The code above actually expands out to
[SerializableAttribute()]
public class MySerializableClass
This shortened name can only be used when the attribute is being applied, in
your code that uses the attribute you must use the full name.
SerializableAttribute [] sa = (SerializableAttribute []) type.GetCustomAttributes(typeof(SerializableAttribute), false);
Note here I had to give the full name just as I would with any other class.
More complex attributes
The constructor for MarshalAs is defined as public
MarshalAsAttribute(UnmanagedType);
. Note that it only accepts one
parameter, what about the other properties?
This is the most confusing part about attributes so you may have to do a
couple examples before this part clicks since it deviates from normal C style
methods and gets into a VB-esque style.
You set the properties in the constructor, after all the parameters for the
constructor. For example, we're going to pass an array of three 32-bit
ints to a method, I'll omit the actual void MyMethod
garbage since you
can see that above
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I4)]
I don't want to weigh you down with the specifics of MarshalAs
since I just
want to use it as an example, but you can see that the first thing passed in was
a member of the UnmanagedType
enum, followed by setting of two properties
SizeConst
and ArraySubType
. This is how all attributes will
work, if the constructor doesn't allow you to set the property, you can set it
by doing so after the parameters of the constructor.
VB programmers will remember this as passing by name.
Creating your own attributes
Ok, you've seen how to use an attribute, how about making one?
If you're using VS.NET create a new console project called TestAttribute and
remove any files that are added to it, we'll be replacing them with our own
versions. If you are using the .NET Framework SDK just create a new
directory to house the project.
Commented versions of the code can be found in the source download.
Project Guidelines
The attribute we will create here will have an imaginary importance in our
demo project. Let's say that it serves as a flag for our program to do
something with the class it is applied to, the number signifying what method of
testing to use. We also wish to have the class name itself, but this isn't
required.
There will be two test classes, one with the default name, and the other
specifying its name.
The driver class will search for our attribute on objects passed in. If
the attribute exists it will printout the classname and call the attributes
PrintOut method. If the attribute doesn't exist, it will print out a
message saying that it doesn't exist.
Open up a new file called TestAttribute.cs (create a new file called
TestAttribute.cs if you're using VS.NET) and put the following code into it
TestAttribute.cs
using System;
namespace Test
{
public class TestAttribute : Attribute
{
public int TheNumber;
public string Name;
public TestAttribute(int number)
{
TheNumber = number;
}
public void PrintOut()
{
Console.WriteLine("\tTheNumber = {0}", TheNumber);
Console.WriteLine("\tName = \"{0}\"", Name);
}
}
}
Thats all there is to your most basic of attributes, data is defined on
it and a single method is there to print out the values of those fields.. As you can see its just a class that inherits from System.Attribute,
and it has two public fields, these could have easily been properties if we
wanted, but my focus was on as little code as possible.
Now lets go use it!
TestClasses.cs
Create a file called TestClasses.cs (add a new class called TestClasses if you're
using VS.NET) and add the following code.
using System;
namespace Test
{
[Test(3)]
public class TestClassA
{
public TestClassA()
{
}
}
[Test(4, Name = "TestClassB")]
public class TestClassB
{
public TestClassB()
{
}
}
}
Here we have defined two classes that our attribute uses.
Now lets add the driver so we can see that the attribute truly is there and
how we go about accessing it!
Driver.cs
Create a new file called Driver.cs (add a new class called Driver if you're
using VS.NET) and add the following code
using System;
namespace Test
{
public class Driver
{
public static void Main(string [] Args)
{
TestClassA a = new TestClassA();
TestClassB b = new TestClassB();
string c = "";
PrintTestAttributes(a);
PrintTestAttributes(b);
PrintTestAttributes(c);
}
public static void PrintTestAttributes(object obj)
{
Type type = obj.GetType();
TestAttribute [] AttributeArray =
(TestAttribute []) type.GetCustomAttributes(typeof(TestAttribute),
false);
Console.WriteLine("Class:\t{0}", type.Name);
if( AttributeArray.Length == 0 )
{
Console.WriteLine("There are no TestAttributes applied to this class {0}",
type.Name);
return ;
}
TestAttribute ta = AttributeArray[0];
ta.PrintOut();
}
}
}
Now compile the program on the command line by running csc like so csc /out:test.exe /target:exe *.cs
When you execute the program you should get output like the image at the top
shows.
How it works
All of the work is done by the framework with the call to GetCustomAttributes
on the Type object. GetCustomAttributes takes two parameters, the first is
a Type object for the attribute we wish to get, the second is a boolean telling
the framework whether it should look at the types that this class derives from.
GetCustomAttributes returns an object array containing each of the attributes
found that match the Type passed in. In this case we requested all
attributes of a specific type, so we can safely cast that to an array of our
attribute. At the very least we could cast that to an array of
Attribute's. If you wish to get all attributes attached to a type
you just pass in value for the bool.
If you look up the GetCustomAttributes method you'll see that it is defined
on the MemberInfo
class. This class is an abstract class which has several
derivatives; Type
, EventInfo
, MethodBase
, PropertyInfo
, and
FieldInfo
.
Each of those classes represents a part of the very basics for the type system.
Right now the TestAttribute
can be applied to all those parts of a class, what
if TestAttribute
only makes sense to be applied to classes? Enter the
AttributeUsage
attribute.
Once it has gotten an array of TestAttribute's
it determines how many are in
the array; if there are none in the array we know that the class doesn't have
the TestAttribute
applied to it; else we take the first one in the array and
tell it to output its values by the PrintOut
method we created in the
TestAttribute
class.
AttributeUsage Attribute
This attribute is used at compile time to ensure that attributes can only be
used where the attribute author allows. This attribute also allows the
author to specify whether the same attribute can be applied multiple times to
the same object and if the attribute applies to objects that derive from the
class it is applied to.
A simple modification to TestAttribute.cs restricts our attribute to being
applied only to classes.
....
[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute : Attribute
....
Now the TestAttribute can only be applied to classes. By default
attributes aren't inherited and can only be applied once so we needn't do
anything more with it.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
The usage above would allow the attribute to be applied to only classes and
structs.
Below is the list of all the flags in the AttributeTargets
enumeration.
The enumeration is marked as Flags so you can combine two or more different
values to allow the attribute to be applied to more than one element.
AttributeTargets |
All |
Assembly |
Class |
Constructor |
Delegate |
Enum |
Event |
Field |
Interface |
Method |
Module (this refers to a .NET executable
file, not a VB module) |
Parameter |
Property |
ReturnValue |
Struct |
Want a brain teaser? Look at the documentation for the AttributeUsage
attribute, notice that it too has the AttributeUsage
attribute applied to it...
So which came first? The AttributeUsage
attribute or the AttributeUsage
attribute!
Summary
Attributes can be attached to virtually everything, classes, methods, fields,
properties, parameters in methods, return values of methods; having at least a
basic knowledge of what attributes are and how to use them is extremely helpful
in the long run. In reading this article you should have learned how to
use them as well as create basic attributes that you can use in your own code.
As usual, any comments or questions post below or e-mail them.
Updates
- February 16, 2002
- Added the AttributeTargets enumeration list
- Tried to clarify the shorthand method used when applying attributes
- Cleared up the Driver's role in the Project
Guidelines
- February 19, 2002
- Fixed the links to the downloads; I used FrontPage for creating the basic HTML and when I added the links I still had the zip files renamed so i could upload them elsewhere