AddThis Social Bookmark Button

Print

Programming C#: Attributes and Reflection
Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9

Using an Attribute

Once you have defined an attribute, you can put it to work by placing it immediately before its target. To test the BugFixAttribute of the preceding example, the following program creates a simple class named MyMath and gives it two functions. You'll assign BugFixAttributes to the class to record its code-maintenance history:



[BugFixAttribute(121,"Jesse Liberty","01/03/05")]
[BugFixAttribute(107,"Jesse Liberty","01/04/05", 
    Comment="Fixed off by one errors")]
public class MyMath

These attributes will be stored with the metadata. Example 18-1 shows the complete program.


Example 18-1: Working with custom attributes

namespace Programming_CSharp
{
  using System;
  using System.Reflection;
 
  // create custom attribute to be assigned to class members
  [AttributeUsage(AttributeTargets.Class |
     AttributeTargets.Constructor |
     AttributeTargets.Field |
     AttributeTargets.Method |
     AttributeTargets.Property,
     AllowMultiple = true)]
  public class BugFixAttribute : System.Attribute
  {
    // attribute constructor for
    // positional parameters
    public BugFixAttribute
      (int bugID,
      string programmer,
      string date)
    {
      this.bugID = bugID;
      this.programmer = programmer;
      this.date = date;
    }
 
    // accessor
    public int BugID
    {
      get
      {
        return bugID;
      }
    }
 
    // property for named parameter
    public string Comment
    {
      get
      {
        return comment;
      }
      set
      {
        comment = value;
      }
    }
 
    // accessor
    public string Date
    {
      get
      {
        return date;
      }
    }
 
    // accessor
    public string Programmer
    {
      get
      {
        return programmer;
      }
    }
    // private member data
    private int   bugID;
    private string comment;
    private string date;
    private string programmer;
  }
 
 
  // ********* assign the attributes to the class ********
 
  [BugFixAttribute(121,"Jesse Liberty","01/03/05")]
  [BugFixAttribute(107,"Jesse Liberty","01/04/05",
     Comment="Fixed off by one errors")]
  public class MyMath
  {
    public double DoFunc1(double param1)
    {
      return param1 + DoFunc2(param1);      
    }
 
    public double DoFunc2(double param1)
    {      
      return param1 / 3;
    }
  }
 
  public class Tester
  {
    public static void Main( )
    {
      MyMath mm = new MyMath( );
      Console.WriteLine("Calling DoFunc(7). Result: {0}",
        mm.DoFunc1(7));
    }    
  }
}

Output:

Calling DoFunc(7). Result: 9.3333333333333339


As you can see, the attributes had absolutely no impact on the output. In fact, for the moment, you have only my word that the attributes exist at all. A quick look at the metadata using ILDasm does reveal that the attributes are in place, however, as shown in Figure 18-1. We'll see how to get at this metadata and use it in your program in the next section.

Screen shot.
Figure 18-1. The metadata in the assembly

Reflection

For the attributes in the metadata to be useful, you need a way to access them -- ideally during runtime. The classes in the Reflection namespace, along with the System.Type and System.TypedReference classes, provide support for examining and interacting with the metadata.

Reflection is generally used for any of four tasks:

Viewing metadata
This might be used by tools and utilities that wish to display metadata.
Performing type discovery
This allows you to examine the types in an assembly and interact with or instantiate those types. This can be useful in creating custom scripts. For example, you might want to allow your users to interact with your program using a script language, such as JavaScript, or a scripting language you create yourself.
Late binding to methods and properties
This allows the programmer to invoke properties and methods on objects dynamically instantiated based on type discovery. This is also known as dynamic invocation.
Creating types at runtime (Reflection Emit)
The ultimate use of reflection is to create new types at runtime and then to use those types to perform tasks. You might do this when a custom class, created at runtime, will run significantly faster than more generic code created at compile time. An example is offered later in this chapter.

Viewing MetaData

In this section, you will use the C# Reflection support to read the metadata in the MyMath class.

You start by initializing an object of the type MemberInfo. This object, in the System.Reflection namespace, is provided to discover the attributes of a member and to provide access to the metadata:

System.Reflection.MemberInfo inf = typeof(MyMath);

You call the typeof operator on the MyMath type, which returns an object of type Type, which derives from MemberInfo.

TIP: The Type class is the root of the reflection classes. Type encapsulates a representation of the type of an object. The Type class is the primary way to access metadata. MemberInfo derives from Type and encapsulates information about the members of a class (e.g., methods, properties, fields, events, etc.).

The next step is to call GetCustomAttributes on this MemberInfo object, passing in the type of the attribute you want to find. What you get back is an array of objects, each of type BugFixAttribute:

object[] attributes;
attributes =
  inf.GetCustomAttributes(typeof(BugFixAttribute),false);

You can now iterate through this array, printing out the properties of the BugFixAttribute object. Example 18-2 replaces the Tester class from Example 18-1.


Example 18-2: Using Reflection

public static void Main( )
{
  MyMath mm = new MyMath( );
  Console.WriteLine("Calling DoFunc(7). Result: {0}",
    mm.DoFunc1(7));
 
  // get the member information and use it to
  // retrieve the custom attributes
  System.Reflection.MemberInfo inf = typeof(MyMath);
  object[] attributes;
  attributes =
    inf.GetCustomAttributes(
      typeof(BugFixAttribute), false);
 
  // iterate through the attributes, retrieving the
  // properties
  foreach(Object attribute in attributes)
  {
    BugFixAttribute bfa = (BugFixAttribute) attribute;
    Console.WriteLine("\nBugID: {0}", bfa.BugID);
    Console.WriteLine("Programmer: {0}", bfa.Programmer);
    Console.WriteLine("Date: {0}", bfa.Date);
    Console.WriteLine("Comment: {0}", bfa.Comment);
  }
}

Output:

Calling DoFunc(7). Result: 9.3333333333333339
 
BugID: 121
Programmer: Jesse Liberty
Date: 01/03/05
Comment:
 
BugID: 107
Programmer: Jesse Liberty
Date: 01/04/05
Comment: Fixed off by one errors

When you put this replacement code into Example 18-1 and run it, you can see the metadata printed as you'd expect.

Pages: 1, 2, 3, 4, 5, 6, 7, 8, 9

Next Pagearrow