[ Team LiB ] Previous Section Next Section

18.2 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.

18.2.1 Viewing Metadata

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

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);

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

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. Type derives from MemberInfo 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. You get back 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.3333333333333333

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.

18.2.2 Type Discovery

You can use reflection to explore and examine the contents of an assembly. You can find the types associated with a module; the methods, fields, properties, and events associated with a type, as well as the signatures of each of the type's methods; the interfaces supported by the type; and the type's base class.

To start, load an assembly dynamically with the Assembly.Load( ) static method. The Assembly class encapsulates the actual assembly itself, for purposes of reflection. The signature for the Load( ) method is:

public static Assembly.Load(AssemblyName)

For the next example, pass in the Core Library to the Load( ) method. Mscorlib.dll has the core classes of the .NET Framework:

Assembly a = Assembly.Load("Mscorlib.dll");

Once the assembly is loaded, you can call GetTypes( ) to return an array of Type objects. The Type object is the heart of reflection. Type represents type declarations (classes, interfaces, arrays, values, and enumerations):

Type[] types = a.GetTypes( );

The assembly returns an array of types that you can display in a foreach loop, as shown in Example 18-3. Because this listing uses the Type class, you will want to add a using statement for the System.Reflection namespace.

Example 18-3. Reflecting on an assembly
namespace Programming_CSharp
{
    using System;
    using System.Reflection;

    public class Tester
    {
        public static void Main( )
        {
            // what is in the assembly
            Assembly a = Assembly.Load("Mscorlib.dll");
            Type[] types = a.GetTypes( );
            foreach(Type t in types)
            {
                    Console.WriteLine("Type is {0}", t);
            } 
            Console.WriteLine(
               "{0} types found", types.Length);
        }
    }
}

The output from this would fill many pages. Here is a short excerpt:

Type is System.TypeCode
Type is System.Security.Util.StringExpressionSet
Type is System.Runtime.InteropServices.COMException
Type is System.Runtime.InteropServices.SEHException
Type is System.Reflection.TargetParameterCountException
Type is System.Text.UTF7Encoding
Type is System.Text.UTF7Encoding+Decoder
Type is System.Text.UTF7Encoding+Encoder
Type is System.ArgIterator
1426 types found

This example obtained an array filled with the types from the Core Library and printed them one by one. The array contained 1,426 entries on my machine.

18.2.3 Reflecting on a Type

You can reflect on a single type in the Mscorlib assembly as well. To do so, extract a type from the assembly with the GetType( ) method, as shown in Example 18-4.

Example 18-4. Reflecting on a type
namespace Programming_CSharp
{
    using System;
    using System.Reflection;

    public class Tester
    {
        public static void Main( )
        {
            // examine a single object
             Type theType = 
                Type.GetType(
                   "System.Reflection.Assembly");
             Console.WriteLine(
                "\nSingle Type is {0}\n", theType);
        }
    }    
}

Output:
Single Type is System.Reflection.Assembly
18.2.3.1 Finding all type members

You can ask the Assembly type for all its members using the GetMembers( ) method of the Type class, which lists all the methods, properties, and fields, as shown in Example 18-5.

Example 18-5. Reflecting on the members of a type
namespace Programming_CSharp
{
   using System;
   using System.Reflection;

   public class Tester
   {
      public static void Main( )
      {
         // examine a single object
         Type theType = 
            Type.GetType(
               "System.Reflection.Assembly");
         Console.WriteLine(
            "\nSingle Type is {0}\n", theType);

         // get all the members
         MemberInfo[] mbrInfoArray = 
            theType.GetMembers( );
         foreach (MemberInfo mbrInfo in mbrInfoArray )
         {
            Console.WriteLine("{0} is a {1}", 
               mbrInfo, mbrInfo.MemberType);
         }
      }
   }    
}

Once again, the output is quite lengthy, but within the output you see fields, methods, constructors, and properties, as shown in this excerpt:

Boolean IsDefined(System.Type, Boolean) is a Method
System.Object[] GetCustomAttributes(Boolean) is a Method
System.Object[] GetCustomAttributes(System.Type, Boolean) is a Method
System.Security.Policy.Evidence get_Evidence( ) is a Method
System.String get_Location( ) is a Method
18.2.3.2 Finding type methods

You might want to focus on methods only, excluding the fields, properties, and so forth. To do so, remove the call to GetMembers( ):

MemberInfo[] mbrInfoArray = 
    theType.GetMembers(BindingFlags.LookupAll);

and add a call to GetMethods( ):

mbrInfoArray = theType.GetMethods( );

The output now is nothing but the methods:

Output (excerpt):
Boolean Equals(System.Object) is a Method
System.String ToString( ) is a Method
System.String CreateQualifiedName(
      System.String, System.String) is a Method
Boolean get_GlobalAssemblyCache( ) is a Method
18.2.3.3 Finding particular type members

Finally, to narrow it down even further, you can use the FindMembers method to find particular members of the type. For example, you can narrow your search to methods whose names begin with the letters Get.

To narrow the search, use the FindMembers method, which takes four parameters:

MemberTypes

A MemberTypes object that indicates the type of the member to search for. These include All, Constructor, Custom, Event, Field, Method, Nestedtype, Property, and TypeInfo. You will also use the MemberTypes.Method to find a method.

BindingFlags

An enumeration that controls the way searches are conducted by reflection. There are a great many BindingFlag values, including IgnoreCase, Instance, Public, Static, and so forth.

MemberFilter

A delegate (see Chapter 12) that is used to filter the list of members in the MemberInfo array of objects. The filter you'll use is Type.FilterName, a field of the Type class used for filtering on a name.

Object

A string value that will be used by the filter. In this case you'll pass in "Get*" to match only those methods that begin with the letters Get.

The complete listing for filtering on these methods is shown in Example 18-6.

Example 18-6. Finding particular members
namespace Programming_CSharp
{
   using System;
   using System.Reflection;

   public class Tester
   {
      public static void Main( )
      {
         // examine a single object
         Type theType = Type.GetType(
            "System.Reflection.Assembly");

         // just members which are methods beginning with Get
         MemberInfo[] mbrInfoArray =
            theType.FindMembers(MemberTypes.Method, 
               BindingFlags.Public |
               BindingFlags.Static |
               BindingFlags.NonPublic |
               BindingFlags.Instance |
               BindingFlags.DeclaredOnly,
               Type.FilterName, "Get*");
         foreach (MemberInfo mbrInfo in mbrInfoArray )
         {
            Console.WriteLine("{0} is a {1}", 
               mbrInfo, mbrInfo.MemberType);
         }
      }
   }
}

Output (excerpt):
System.Type[] GetTypes( ) is a Method
System.Type[] GetExportedTypes( ) is a Method
System.Type GetType(System.String, Boolean) is a Method
System.Type GetType(System.String) is a Method
System.Reflection.AssemblyName GetName(Boolean) is a Method
System.Reflection.AssemblyName GetName( ) is a Method

18.2.4 Late Binding

Once you have discovered a method, it's possible to invoke it using reflection. For example, you might like to invoke the Cos( ) method of System.Math, which returns the cosine of an angle.

You could, of course, call Cos( ) in the normal course of your code, but reflection allows you to bind to that method at runtime. This is called late binding and offers the flexibility of choosing at runtime which object you will bind to and invoking it programmatically. This can be useful when creating a custom script to be run by the user or when working with objects that might not be available at compile time. For example, by using late binding, your program can interact with the spellchecker or other components of a running commercial word processing program such as Microsoft Word.

To invoke Cos( ), you will first get the Type information for the System.Math class:

Type theMathType = Type.GetType("System.Math");

With that type information, you could dynamically load an instance of a class by using a static method of the Activator class. Since Cos( ) is static, you don't need to construct an instance of System.Math (and you can't, since System.Math has no public constructor).

The Activator class contains four methods, all static, which you can use to create objects locally or remotely, or to obtain references to existing objects. The four methods are:

CreateComInstanceFrom

Used to create instances of COM objects.

CreateInstanceFrom

Used to create a reference to an object from a particular assembly and type name.

GetObject

Used when marshaling objects. Marshaling is discussed in detail in Chapter 19.

CreateInstance

Used to create local or remote instances of an object.

For example:

Object theObj = Activator.CreateInstance(someType);

Back to the Cos( ) example, you now have one object in hand: a Type object named theMathType, which you created by calling GetType.

Before you can invoke a method on the object, you must get the method you need from the Type object, theMathType. To do so, you'll call GetMethod( ), and you'll pass in the signature of the Cos method.

The signature, you will remember, is the name of the method (Cos) and its parameter types. In the case of Cos( ), there is only one parameter: a double. However, Type.GetMethod takes two parameters. The first represents the name of the method you want, and the second represents the parameters. The name is passed as a string; the parameters are passed as an array of types:

MethodInfo CosineInfo = 
   theMathType.GetMethod("Cos",paramTypes);

Before calling GetMethod( ), you must prepare the array of types:

Type[] paramTypes = new Type[1];
paramTypes[0]= Type.GetType("System.Double");

This code declares the array of Type objects and then fills the first element (paramTypes[0]) with a Type representing a double. Obtain the type representing a double by calling the static method Type.GetType( ), and passing in the string "System.Double".

You now have an object of type MethodInfo on which you can invoke the method. To do so, you must pass in the object to invoke the method on and the actual value of the parameters, again in an array. Since this is a static method, pass in theMathType. (If Cos( ) was an instance method, you could use theObj instead of theMathType.)

Object[] parameters = new Object[1];
parameters[0] = 45 * (Math.PI/180); // 45 degrees in radians
Object returnVal = CosineInfo.Invoke(theMathType,parameters);

Note that you've created two arrays. The first, paramTypes, holds the type of the parameters. The second, parameters, holds the actual value. If the method had taken two arguments, you'd have declared these arrays to hold two values. If the method did not take any values, you would still create the array, but you would give it a size of zero!

Type[] paramTypes = new Type[0];

Odd as this looks, it is correct.

Example 18-7 illustrates dynamically calling the Cos( ) method.

Example 18-7. Dynamically invoking a method
namespace Programming_CSharp
{
   using System;
   using System.Reflection;

   public class Tester
   {
      public static void Main( )
      {
         Type theMathType = Type.GetType("System.Math");
         // Since System.Math has no public constructor, this
         // would throw an exception.
         //Object theObj = 
         //   Activator.CreateInstance(theMathType);

         // array with one member
         Type[] paramTypes = new Type[1];
         paramTypes[0]= Type.GetType("System.Double");

         // Get method info for Cos( )
         MethodInfo CosineInfo = 
            theMathType.GetMethod("Cos",paramTypes);

         // fill an array with the actual parameters
         Object[] parameters = new Object[1];
         parameters[0] = 45 * (Math.PI/180); // 45 degrees in radians
         Object returnVal = 
            CosineInfo.Invoke(theMathType,parameters);
         Console.WriteLine(
            "The cosine of a 45 degree angle {0}", 
            returnVal);
 
      }
   }
}

That was a lot of work just to invoke a single method. The power, however, is that you can use reflection to discover an assembly on the user's machine, to query what methods are available, and to invoke one of those members dynamically.

    [ Team LiB ] Previous Section Next Section