21 - Attributes and Reflection in C#

21.1 Introducing Attributes

An object is defined by using the value of its attributes. A C# program has certain attributes which influence the behavior. An attribute is a declarative tag used to convey information to the runtime about the behavior of the elements such as classes, enumerators and assemblies.

They are used for adding metadata, such as compiler instructions and other information as comments, descriptions, methods and classes to the program.

The .NET framework contains a number of predefined attributes. The code to test the predefined attributes and act upon the values they contain is also added as a part of the runtime and the Software Development Kit (SDK).

21.2 Attributes Parameters

Attributes are applied to different elements of the code. These elements contain assemblies, modules, classes, structs, enums, constructors, methods, properties, fields, events, interfaces, parameters and delegates. The attributes information is stored in the metadata of the associated elements.

The following syntax specifies an attribute:

    [ attribute ( positional_parameters , name_parameter = value, … ) ] element

In the above syntax, an attribute name and its value are specified within the square brackets ([ ]) before the element to which the attribute is applied. They might require one or more parameters, positional or named. Positional parameters are used to specify information about an attribute. Named parameters are used to convey optional information about an attribute.

The .NET framework supports the following two categories of attributes to be used.

1) Predefined attributes: These attributes are supplied as a part of the Common Language Runtime (CLR) and integrated into .NET framework.

2) Custom attributes: These attributes are created according to the requirement.

21.3 Predefined Attributes

Some of the commonly used predefined attributes provided with the .NET framework are:

1) Conditional: It causes conditional compilation of method calls, depending on the specified value such as Debug or Trace. If the conditional compilation of a method is required, the #if and #endif directives are used.

Consider the following line of code:

    [ Conditional ( “DEBUG” ) ] 

The above code ensures that the target element, whether a class or a method ensures use of that element in the debugging mode only.

2) WebMethod: It identifies the exposed method in a Web Service. A Web service is an application that allows the user to expose business functionality to other applications. The following code snippet shows the use of the WebMethod attribute:

    WebMethod ( Description = “Converts temperature from Fahrenheit to Celsius” ) ]
    public double TempConvert ( double myTemp )
    {
       return ( ( myTemp – 32 ) * 5 ) / 9;
    }

 

 

In the above code snippet, a method of a Web service which converts temperature from Fahrenheit to Celsius. The WebMethod attribute definition above TempConvert() method tells the compiler that it is an exposed method and the Description property of the WebMethod attribute provides information about working of the TempConvert() method.

3) DllImport: It calls an unmanaged code in programs developed outside the .NET environment. By using the attribute, the unmanaged code residing in the DLLs can be called from the managed C# environment. The following code shows the use of DllImport attribute:

  

Using System Runtime. InteropServices;

public partial class Default2 : System.Web.UI.Page
{
    class Generate
    {
        [ DllImport ( “user32.dll”, EntryPoint = “MessageBox” ) ]
        public static extern int Message ( IntPtr hWnd, String text. String caption, Unit type );
    }
    public static void Main ()
    {
        MessageBox ( new IntPtr ( 0 )”, “Hello World”, “Hello Dialog”, 0 );
    }
}

The code uses the Message as static method containing a pointer, string type variables containing text and caption and a unit data type.

4) Obsolete: It enables user to inform the compiler to discard the target element such as class or a method. When a new method is used by a class and user wants to retain the old method then it can be marked as Obsolete.

The following code shows the use of the obsolete attribute:

public class MyClass
{
    [Obsolete ( “Do not use the A() method, use the method B() “, true ) ]
    public static void A()
    {
    }
    static void B()
    {
    }
}
public static void Main()
{
    A();
}

In the above code, the first parameter is string that contains a message. The second parameter informs the compiler to use the element as an error.

21.4 Creating Custom Attributes

The creation of custom variables is possible through the .NET framework. The information is stored and can be retrieved at runtime by the user.

A simple code can be created to read through the metadata to find the bug fixing notations and help to update the database. Metadata is data about data. It is used for describing about other data.

To create custom attribute, user needs to:

· Define a custom attribute

· Name of the custom attribute

· Construct the custom attribute

· Apply the custom attribute to a target application

Define a custom attribute

A new custom attribute class is derived from the System.Attribute class as shown in the code snippet below:

    public class BuFixingAttribute: System.Attribute

User needs to apply the attribute target using a predefined attribute to the new custom attribute class, AttributeUsage as shown in the code snippet:

    [AttributeUsage ( AttributeTargets.Class |
     AttributeTargets.Field |
     AttributeTargets.Method| 
     AttributeTargets.Constructor | 
     AttributeTargets.Property , AllowMultiple  = true ) ]

The AttributeUsage attribute accepts two arguments. One argument is a set of flags indicating the target element. The other argument is a flag that indicates whether a given element be applied to more than one attribute. The AllowMultiple attribute is set to true, stating that the attribute can be applied to the class and members.

The other properties that can be used within the attributes are as follows:

1) Inherited: The property helps to define inheritance for an attribute. A value true indicates that the attribute is applied to the base and derived classes. A false attribute indicates that the attribute cannot be applied to the derived classes.

2) ValidOn: The target elements on which the custom attributes are defined are applied. The attribute target is the element on which the target is applied.

The list of members names of the AttributeTargets enumerator are as shown below:

Member Name

Attribute Targets

All

These elements include assembly, events, class, class members, field, method, module, parameter, property or struct.

Assembly

It is associated with assembly2) ValidOn: only.

Class

It is the instance of a class

ClassMembers

The Classes, structs, enums, constructors, properties, fields, method, delegates, events and interfaces are included.

Constructor

It defines the constructor of a class.

Delegate

It contains the Delegate methods

Enum

It defines the enumerations

Event

It defines the event

Field

It defines the fields

Interface

It defines the interface

Methods

It contains the defined methods

Property

It defines the property

Struct

It contains the Structs

Constructing the Custom Attribute

Every attribute contains at least one constructor. The following code snippet shows the creation of custom attribute.

[ AttrbuteUsage ( AttributeTargets.Class 
| AttributeTargets.Method 
| AttributeTargets.Constructor 
| AttributeTargets.Property, AllowMultiple = true ) ]

public class BugFixngAtribute : System.Attribute
{
    public BugFixingAttribute ( int BugNo, string Developer, string FixedDate )
    {
    }
    public string Remarks 
    {
        get
        {
            return remarks;
        }
        set
        {
            Remarks = value;
        }
    }
    public string BugNo 
    {
        get
        {
            return BugNo;
        }
    }
    public string Develper
    {
        get
        {
            return Developer;
        }
    }
    public string FixedDate 
    {
        get
        {
            return FixedDate;
        }
    }
}

In the following code snippet, the bug number, developers name, and fixed data are the positional parameters and remarks is the named parameter. The positional parameters are passed through the constructor. The named parameter is implemented as properties and the positional parameters are declared as read – only parameters.

21.5 Applying Custom Attribute in an application

The attribute can be applied by placing before the target. Create a program containing a class Calculate and provide the functions The BugFixingAttribute will be stored in the metadata.

class Program
{
    [AttributeUsage ( AttributeTargets.Class 
        | AttributeTargets.Constructor 
        | AttributeTargets.Field
        | AttributeTargets.Method 
        | AttributeTargets.Property, AllowMultiple = true ) ]

        public class BugFixingAttribute: System.Attribute
        {
           private int bugno;
           private string developer;
           private string datefixed;
           public string remarks;
 
             public class BugFixingAttribute ( int BugNo, string Developer, string DateFixed )
             {
               this.bugno = BugNo;
               this.developer= Developer;
               this.dateFixed = DateFixed;
             }
             public int BugNo
             {
               get
               {
                 return bugno;
               }
             }
            public string DateFixed
            {
              get
              {
                return dateFixed;
              }
             {
            public int Developer
            {
               get
               {
                return developer;
               }
            }
           public string Marks
           {
              get
              {
               return marks;
              }
            }
      }
}

[ BugFixingAttribute ( 1, “John”, “08/01/1986”, remarks = “Return object not specified” ) ]
[ BugFixingAttribute ( 2, “Mark”, “09/02/2010” remarks = “Data Type not matched”) ]
public class Calculate
{
    public double Add ( double no1, double no2)
    {
        return no1 + no2;
    }
    public double Substract ( double no3, double no4)
    {
        return no3 – no4 ;
    }
    [BugFixingAttribute( 6,”John”, “09/02/2010”) ]
    public double Multiply( double no5, double no6 )
    {
        return no5 * no6 ;
    }
    [BugFixingAttribute( 7,”Mark”, “07/05/2011”) ]
    public double Divide( double no7, double no8 )
    {
        return no7 /no8 ;
    }
}
public class EntryPoint
{
    static void Main ( string[ ] args )
    {
        Calculate c = new Calculate();
        Program p = new Program();
        Console.WriteLine(“The sum of two nos is : {0}”, c.Add(10,10) );
        Console.Read();
    }
}

}

The output for the code is as shown below:

After executing the above code, the program output would be displayed. Open the file with .exe extension using the MSIL Disassembler. Following are the steps to perform the operations in C#.

1) Select ‘Start’ > ‘All Programs’ > ‘Microsoft .NET Framework SDK v2.0’ > ‘Tools’ > ‘MSIL Disassembler’.

2) Select ‘File’ > ‘Open’. The Open dialog box is displayed

3) Select the .exe file from the Debug folder of the project

4) Click ‘OK’ button. The IL DASM window is displayed

5) Expand the node and the details are as shown below:

21.6 Introducing Reflection

In C#, reflection is used to reflect all the information about a program. The programs make use of the reflection to get the class information at the runtime. They can also reflect the information about a program.

Reflection is used in the process of obtaining type information at runtime. The classes that provide the information of a running program are in the System.Reflection namespace.

The namespace contains classes that allow the programmers to obtain information about the application that is executing and add types, values and objects dynamically to that application.

The term reflection is used for the following purposes:

1) Viewing metadata: Allows the user to view the attribute information from the code at runtime.

2) Performing type discovery: It allows the user to examine the various types in an assembly and instantiate them.

3) Late binding to the methods and properties: It allows the user to call properties and methods on dynamically instantiated objects using the type discovery.

4) Reflection emit: It allows the user to create new types at runtime and use them to perform the tasks.

21.7 Viewing Metadata

The viewing of metadata is possible through the MemberInfo object of the System.Reflection namespace. The object helps in discovering the attributes of a member and access to the metadata. Reflection can be shown by taking the bug fixing example and read metadata in the Calculate class, as shown in the following snippet:

      Type type = typeof ( Calculate );

In the above code, the typeof operator on the Calculate type returns the object type of the Type class. The Type class is the root of the reflection class.

The following code snippet is used to demonstrate the modified Main() method of the BugFixing example:

public class EntryPoint
{
    static void Main ( string[ ] args )
    {
        Calculate c = new Calculate();
        Console.WriteLine(“The sum of the two nos is : {0}”, c.Add(10,10) );
        Type type = typeof( Calculate );

         foreach ( Object attributes in type.GetCustomAttributes ( false ) )
        {
            BugFixingAttribute myobj = ( BugFixingAttribute) attributes;
            if ( null !=myobj )
            {
                Console.WriteLine(“\nBug#:{0}”, myobj.BugNo);
                Console.WriteLine(“Developer:{0}”, myobj.Developer);
                Console.WriteLine(“Fixed Date:{0}”, myobj.DateFixed);
                Console.WriteLine(“Remarks:{0}”, myobj.Remarks);
            }
        }

        foreach ( _MethodInfo method in type.GetMethods() )
        {
            foreach ( Attribute attributes in method.GetCustomAtttributes )
            {
                BugFixingAttribute myobjM = ( BugFixingAttribute ) attributes;
                if ( null ! = myobjM )
                {
                    Console.WriteLine( “\n Bug#:{0} for Method{1}”, myobjM.BugNo, method.Name);
                    Console.WriteLine(“Developer:{0}”, myobjM.Developer);
                    Console.WriteLine(“Date Fixed:{0}”, myobjM.DateFixed);
                    Console.WriteLine(“Remarks:{0}”, myobjM.Remarks);
                }
            }
            Console.Read();
        }
    }
}

The output for the code is as shown below:

Like us on Facebook