Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Creating and Using Attributes in your .NET application

0.00/5 (No votes)
9 Feb 2002 1  
Shows how to use existing attributes and how to create and use your own attributes

Screenshot of the console output, using the web palette for Colin and Paul :-)

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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here