Google
 

Monday, June 11, 2007

Creating and Using Attributes in your .NET application

ref: http://www.codeproject.com/csharp/dotnetattributes.asp

What are attributes

Essentially attributes are a means of decorating your code with various properties at compile time.

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 <>.


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.

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.

More complex attributes

You set the properties in the constructor, after all the parameters for the constructor.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.I4)]
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.

Creating your own attributes

TestAttribute.cs
using System;

namespace Test
{
// Only allow this attribute to be added to classes
[AttributeUsage(AttributeTargets.Class)]
public class TestAttribute : Attribute
{
// A number with some imaginary importance
public int TheNumber;

// A string that could be useful somewhere
public string Name;

// The only constructor requiring that
// TheNumber be set
public TestAttribute(int TheNumber)
{
this.TheNumber = TheNumber;
Name
= "None";
}

// Method to illustrate that an attribute is really just
// a class at heart. This will be used in Driver.cs
public void PrintOut()
{
Console.WriteLine(
"\tTheNumber = {0}", TheNumber);
Console.WriteLine(
"\tName = \"{0}\"", Name);
}
}
}

TestClasses.cs

using System;

namespace Test
{
// A test class setting TheNumber to 3, leaving Name to its default
[Test(3)]
public class TestClassA
{
public TestClassA()
{

}
}

// A test class setting TheNumber to 4, and setting Name to "TestClassB"
[Test(4, Name = "TestClassB")]
public class TestClassB
{
public TestClassB()
{

}
}
}


Driver.cs

using System;

namespace Test
{
public class Driver
{
// Entry point of the program
public static void Main(string [] Args)
{
// We really don't have to create an instance of TestClass*, remember
// attributes are properties applied to Type's and the parts that make
// up types (fields, properties, methods, return values, parameters).
TestClassA a = new TestClassA();
TestClassB b
= new TestClassB();
string c = "";

PrintTestAttributes(a);
// Should print out the number 3 and an empty string, since
// none was specified on the class
PrintTestAttributes(b); // Should print out the number 4 and the name of the class
PrintTestAttributes(c); // Shouldn't print out that TestAttribute exists

Console.Read();
}

// Prints out the values for the TestAttribute on the object
// if the TestAttribute isn't applied it prints out a message
// indicating such
public static void PrintTestAttributes(object obj)
{
Type type
= obj.GetType(); // Get the underlying type from the object passed in
// attributes are decorations on Type's so we need
// a Type object to operate on.

// Get the attributes applied to this object of type
// TestAttribute, this returns an array of TestAttributes, one for
// each TestAttribute applied to the class.
// The code will only expect 0 or 1 TestAttributes to be applied
// The false parameter tells the runtime to only search this type, none of the base types for the attribute
TestAttribute [] AttributeArray = (TestAttribute []) type.GetCustomAttributes(typeof(TestAttribute), false);

// Class name
Console.WriteLine("Class:\t{0}", type.Name);

// If no attributes were returned, print out the message
if( AttributeArray.Length == 0 )
{
Console.WriteLine(
"There are no TestAttributes applied to this class");
return ;
}

// Retrieve the one and only instance of TestAttribute from class
TestAttribute ta = AttributeArray[0];

// Called the TestAttribute's PrintOut method
ta.PrintOut();
}
}
}


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.

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.

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

No comments: