Tuesday, March 24, 2009

Attributes in C#

Attributes adds the information to the program entities such as an Assembly, Module, Class, Struct, Enum, Constructor, Method, Property, Field, Event, Interface, Parameter, Delegate. The information that Attribute contains is inserted into the assembly, at run time. The compiler can retrieve/queried this information at run-time using reflection.

Using predefined attributes:

There can be predefined or user-defined/custom attributes. Before digging into custom attributes we will have a quick look at using user-defined attributes, some of them are:

System.AttributeUsageAttribute - Describe the ways in which an attribute class can be used, we are going to know how to use it in a few minutes in custom attributes section.

System.Diagnostics.ConditionalAttribute - Used to define conditional methods.

Conditional Attribute is used to create conditional methods; this method is being called if a specific preprocessor symbol is defined using #define, if not the method will be bypassed/resumed. The conditional method much return void, it should be the member of a class rather than an Interface and we cannot override it. Below is the example on this.

#define MYCONSTANT
using System;
using System.Diagnostics; //We must use this namespace for using conditional attributes

public class MyClass {

[Conditional("MYCONSTANT")]
public void MyMethod1( ) {
Console.WriteLine("MyMethod1 - MYCONSTANT is defined");
}

[Conditional("MYCONSTANT")]
public void MyMethod2( ) {
Console.WriteLine("MyMethod2 - DEBUG");
}
}

public class TestClass {
public static void Main( ) {
MyClass obj = new MyClass( );
obj.MyMethod1( ); //this is called only when MYCONSTANT is defined
obj.MyMethod2( ); //this method will be called when the Compile parameter is being used from command prompt e.g. "csc Program.cs /d:DEBUG"
}
}


System.Runtime.InteropServices.DllImportAttribute - DllImportAttribute attribute provides the information needed to call a function exported from an unmanaged DLL, example below tells how to use DllImport to import the Win32 MessageBox function.

using System;
using System.Runtime.InteropServices;

class Example
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);

static void Main()
{
// Call the MessageBox function using platform invoke.
MessageBox(new IntPtr(0), "Hello World!", "Hello Dialog", 0);
}
}

System.ObsoleteAttribute - Used to mark a member as deprecated or obsolete, see below is the example on this.

namespace ConsoleApplication1
{
class TestClass
{
[Obsolete("Do not use Method1 its old, use Method2", true)]
static void Method1()
{
}

static int Method2()
{
return 0;
}

static void Main()
{
Method1(); //this will give compile Error - ConsoleApplication1.TestClass. Method1()' is obsolete: 'Do not use Method1 its old, use Method2'

int i = Method2(); //No Errors

Console.ReadLine();
}


}
}


User-defined/Custom attributes

Now lets look at making our own custom attributes.


Using the Attribute classes:

Deriving System.Attribute just makes our class an Attribute class. We can use the Attribute convention as a suffix to our custom class like ‘HelpAttribute’ but can be referred only by class name [Help(" this is my description about the program entity")]
Compiler is intelligent enough to add the word Attribute to the specified attribute name and search for it in System.Attribute derived classes first and then in our custom class.

AttributeUsage attribute:

AttributeUsage is used on custom attributes to control the usage of our attribute. We can control to all the program entities using AttributeTargets. ‘AttributeTargets.Class’ is used to allow only for Class declarations; ‘AttributeTargets.Method’ is to allow only for Method declarations and so on. Use ‘AttributeTargets.All’ to allow our custom attributes to be used by the all program entities.

[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false )]

The AllowMultiple = true let us to use the attributes multiple times for the same programme entity. Below is the example:

[Help("This is the test class")]
[Help("more desc..., this is possible with AllowMultiple=true")]
class TestClass
{...}


if AllowMultiple = false, then you will get the compiler error as ‘Duplicate 'Help' attribute’ for the above code. The property Inherited = true let the base class attributes inherited to the derived classes. Further more we can use multiple AttributeTargets like below.

[AttributeUsageAttribute(AttributeTargets.PropertyAttributeTargets.FieldAttributeTargets.ParameterAttributeTargets.ReturnValue, AllowMultiple = false)]
public class XmlArrayAttribute : Attribute


Positional parameters and Named parameters:

Attribute classes can have positional parameters and named parameters. Positional parameters are defined by public instance constructor of an attribute class and named parameters are defined by non-static public read-write field and property of an attribute class, we called it named because when setting the value we have to name them. In the below example Version is the named parameter where as the description passed through public instance constructor is positional.

[Help("This is the test class", Version="1.2")]
class TestClass
{...}


To give you a better idea they both are shown in the below example.

Limited parameters types:

We can use a limited parameters types for an attribute class they are : bool, byte, char, double, float, int, long, short, string, System.Type, object, public enum.

Attributes Identifiers:

attribute identifiers are used to let the compiler the entity on which it can placing the attribute. the identifiers can be assembly, module, type, method, property, event, field, param, return.
below example tells the compiler to attace the attribute to entire assembly.

[assembly: Help("this is assembly")]

Querying attributes:

Reflection is the ability to get the metadata of the object at run-time; we need to use this to query the attached attributes of the type.

using System;
using System.Reflection;
using System.Diagnostics;

namespace ConsoleApplication1
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
public class HelpAttribute : Attribute
{
protected String description;
public HelpAttribute(string description) // Positional parameter
{
this.description = description;
}

public String Description // Named parameter
{
get
{
return this.description;
}
}

protected String version;
public String Version // Named parameter
{
get
{
return this.version;
}
set
{
this.version = value;
}
}
}

[Help("This is my sample test Class.")]
public class MyClass
{
[Help("This is my String member variable.")]
public string sMember;

[Help("This is my Integer member variable.")]
public int iCounter;

[Help("This is my Integer protected member variable.")]
protected int jCounter;

[Help("This is a my test Method.")]
public void TestMethod1() { }

[Help("This is a my test Method2.")]
public void TestMethod2() { }

[Help("This is a my test Method3", Version = "7.1")]
[Help("more description about the class, this is possible with AllowMultiple=true")]
public void TestMethod3() { }
}

class TestClass
{
static void Main()
{
Type type = typeof(MyClass);
HelpAttribute HelpAttr;

//Querying Class Attributes
foreach (Attribute attr in type.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of MyClass:\n{0}", HelpAttr.Description);
}
}

//Querying Class method attributes
foreach (MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in method.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}", method.Name, HelpAttr.Description);
}
}
}

//Querying Class Attributes for public fields
foreach (FieldInfo field in type.GetFields())
{
foreach (Attribute attr in field.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}", field.Name, HelpAttr.Description);
}
}
}
Console.ReadLine();
}
}
}

No comments:

Post a Comment