Monday, June 18, 2007

Attributes and Reflection in C#

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

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

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

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

Example: 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< mi =" methods[i];" attributes =" mi.GetCustomAttributes(true);" thecomments =" (Comments)attribute;" status = "Complete" employeename =" value;" status = "Complete" basic =" value;" status = "Complete" employee =" new">

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.

Example: 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));
}
}
}

No comments: