Attributes provide a powerful way to extend metadata by
associating declarative information with C# code. The attribute information is
stored with the metadata of the element and can be easily retrieved at runtime
using reflection.
Attribute
An attribute is essentially an object that represents the
data that is associated with a program element. The element to which an
attribute is attached is referred to as the target of that attribute.
Attribute targets can be one of the following:
·
All
·
Assembly
·
Class
·
Constructor
·
Delegate
·
Enum
·
Event
·
Field
·
Interface
·
Method
·
Module
·
Parameter
·
Property
·
ReturnValue
·
Struct
Types of Attributes
Attributes are basically of two types, intrinsic and custom.
Intrinsic Attributes
Intrinsic attributes are supplied as part of the Common
Language Runtime, and they are integrated into .NET.
In this example below we use a pre–defined attribute,
Obsolete, which marks a program entity as obsolete. The attribute accepts two
parameters of which the first is a message and the second a boolean value. If
the second parameter is true, the compiler gives an error if the method is
invoked, and a warning otherwise.
Listing 1: Using the Obsolete Attribute
using System;
public class Test
{
[Obsolete("This method is deprecated. Usethe method Display(string)
instead.", false)]
void Display()
{
}
void Display(string s)
{
}
public static void Main( )
{
Test test = new Test ();
test.Display ();
}
}
Custom Attributes
Custom attributes are attributes that we create for our own
purposes. Attributes are public classes and are initialized using constructors.
Every attribute must have at least one constructor. The constructors can be
overloaded to allow the attribute to be applied to the class in multiple ways.
To create a custom attribute, we have to derive our new
custom Attribute class from the class System.Attribute, as shown below.
Listing 2: Declaring a Custom Attribute Class
using System;
public class Comments : Attribute
{
}
Next, we have to specify the target of the attribute using
the AttributeUsage attribute. An attribute is applied to the target by
specifying the attribute name in brackets as shown in the example below.
Listing 3: Declaring the Custom Attribute Class
public class Comments : System.Attribute[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true)]
AttributeUsage
The scope and target of the attribute can be defined by
applying AttributeUsage. It contains three properties, which we can set to
specify attributes.
AllowOn
This is a set of flags that indicates the program entities
on which the attribute can be placed. Multiple AttributeTargets can also be
specified using a bitwise OR operator.
AllowMultiple
AllowMultiple lets you specify whether you can apply
multiple instances of a particular attribute to the same element. If
AllowMultiple is set to true, then inherited classes will inherit all instances
of the attribute from the parent class. The default value for AllowMultiple is
false.
Inherited
The Inherited property determines whether the attribute will
be inherited by classes that are derived from the classes to which the
attribute is applied. The default value for Inherited is true, indicating that
an attribute applied to the base class will be applied to all its derived
classes.
Attribute Parameters
Attributes accept parameters for customization. They take
two types of parameters, positional and named.
Positional Parameters: Positional parameters are specified
using constructor arguments to the attribute class.
Named Parameters: Named parameters are defined by having a
non-static field or property in the attribute class.
Attribute parameter types can be:
bool, byte, char, double, float, int, long, short, string
System.object
System.Type
A public enum
A one-dimensional array of the above types
Implementing a Custom Attribute class
The following section shows a custom attribute class called
Comments, the application of the attribute to several entities of a class
called Employee, and usage of reflection on this class to retrieve the
attributes already specified. In the Comments class, the parameters author,
type, and description are compulsory positional parameters and status is an
optional named parameter.
Listing 4: Implementing the Custom Attribute Class
using System;
using System.Reflection;
[AttributeUsage( AttributeTargets.All )]
public class Comments : System.Attribute
{
public string author = String.Empty;
public string type = String.Empty;
public string description = String.Empty;
private string status = String.Empty;
public Comments(string author, string type,string description)
{
this.author = author;
this.type = type;
this.description = description;
}
public string Status
{
get
{
return status;
}
set
{
status = value;
}
}
public static void DisplayAttributes(Type t)
{
Comments comments = (Comments) Attribute.GetCustomAttribute(t,typeof
(Comments));
Console.WriteLine("The Author is:{0}." , comments.author);
Console.WriteLine("The Description is:{0}." , comments.description);
System.Console.WriteLine("Class Name:{0}", t.Name);
MethodInfo[] methods = t.GetMethods();
object[] attributes = null;
for (int i = 0, l = methods.GetLength(0); i< l; i++)
{
MethodInfo mi = methods[i];
attributes = mi.GetCustomAttributes(true);
foreach (Attribute attribute in attributes)
{
if (attribute is Comments)
{
Comments theComments = (Comments)attribute;
System.Console.WriteLine("Name: {0} ,Type : {1}, Purpose: {2} , Created
By : {3}", mi.Name, theComments.type ,theComments.description,
theComments.author );
}
}
}
}
}
[Comments("Joydip","Class","Employee Entity")]
class Employee
{
private string employeeName;
private int basic;
[Comments("Joydip","Property","Set/Retrieve name of the employee", Status =
"Complete")]
public string Name
{
get
{
return employeeName;
}
set
{
employeeName = value;
}
}
[Comments("Joydip","Property","Set/Retrieve basic salary of the employee",
Status = "Complete")]
public int Basic
{
get
{
return basic;
}
set
{
basic = value;
}
}
[Comments("Joydip","Method","Display employee details", Status =
"Complete")]
public void Display()
{
Console.WriteLine ("The name is"+basic);
Console.WriteLine ("The basic is"+basic);
}
public static void Main(string[] args)
{
Employee employee = new Employee ();
Comments.DisplayAttributes(typeof(Employee));
System.Console.Read();
}
}
The method DisplayAttributes displays all the attributes set
to the elements of the class Employee using reflection.
Reflection
Reflection provides objects that encapsulate assemblies,
modules, and types. It is the process by which a program can inspect metadata
information dynamically using the reflection API. Using reflection, we can
create instances of a type, bind the type to an existing object, or get the
type from an existing object and invoke its methods or access its fields and
properties. Reflection is much the same as RTTI of native C++ but with a major
difference in that reflection in C# works with managed code and is much more
powerful.
The reflection classes are contained in the namespace
System.Reflection. The classes in the Reflection namespace, along with the
System.Type and System.TypedReference classes, provide support for examining
and interacting with the metadata. The abstract base class Type helps access
metadata information. The types include the constructors, methods, fields,
properties, and events of a class and the module and the assembly in which
these are stored.
The System.Reflection namespace defines the following types:
·
Assembly
·
Module
·
Enum
·
MethodInfo
·
ConstructorInfo
·
MemberInfo
·
ParameterInfo
·
Type
·
FieldInfo
·
EventInfo
·
PropertyInfo
Here is a complete example of a class that inspects another
class and demonstrates the power of reflection to display the metadata
information dynamically.
Listing 5: Reflection in Action
using System;
using System.Reflection;
namespace ReflectionTest
{
public class Employee
{
string name;
public string Name
{
get
{
return name ;
}
set
{
name = value ;
}
}
public Employee()
{
}
public Employee ( string name )
{
this.name = name;
}
public void Display ()
{
}
}
public class Reflect
{
public static void DisplayDetails(Type type)
{
Console.WriteLine ( "Class: " + type) ;
Console.WriteLine ();
Console.WriteLine ( "Namespace: " +type.Namespace ) ;
Console.WriteLine ();
ConstructorInfo[] constructorInfo =
type.GetConstructors( );
Console.WriteLine( "Constructors:--") ;
foreach( ConstructorInfo c in constructorInfo)
{
Console.WriteLine( c ) ;
}
PropertyInfo[] propertyInfo =type.GetProperties( );
Console.WriteLine ();
Console.WriteLine( "Properties:--" );
foreach( PropertyInfo p in propertyInfo )
{
Console.WriteLine( p ) ;
}
MethodInfo[] methodInfo = type.GetMethods( ) ;
Console.WriteLine ();
Console.WriteLine( "Methods:--" ) ;
ParameterInfo[] parameterInfo = null;
foreach( MethodInfo m in methodInfo )
{
Console.WriteLine( "Method Name: "+ m.Name ) ;
parameterInfo = m.GetParameters () ;
foreach ( ParameterInfo p in parameterInfo )
{
Console.WriteLine("Parameter Type:" + p.ParameterType + " Parameter
Name: " + p.Name ) ;
}
}
}
public static void Main ( string[] args )
{
DisplayDetails(typeof(Employee));
}
}
}
Conclusion
C# is an imperative language, but it also offers scope for
adding declarative features to the code and their later retrieval if necessary.
We can use attributes to provide both design-level and run-time information to
our code.
For Further Reading
http://www.oreilly.com/catalog/progcsharp/chapter/ch18.html
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkattributestutorial.asp
http://www.vijaymukhi.com/documents/books/csbasics/chap14.htm
http://www.c-sharpcorner.com/1/CustomAttribute.asp
http://www.ondotnet.com/pub/a/dotnet/excerpt/prog_csharp_ch18/index.html?page=1