Section 18.2. Reflection


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 class, 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.

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 obtaining 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 heart 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. One 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");

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 example uses the Type class, you will want to add a using directive for the System.Reflection namespace.

Example 18-3. Reflecting on an assembly
#region Using directives using System; using System.Collections.Generic; using System.Reflection; using System.Text; #endregion namespace ReflectingAnAssembly {    public class Tester    {       public static void Main( )       {          // what is in the assembly          Assembly a = Assembly.Load( "Mscorlib" );          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.Object Type is ThisAssembly Type is AssemblyRef Type is System.ICloneable Type is System.Collections.IEnumerable Type is System.Collections.ICollection Type is System.Collections.IList Type is System.Array 2373 types found

This example obtained an array filled with the types from the core library and printed them one by one. The array contained 2,373 entries on my machine.

In Version 1.1, I found 1,426 entries on my machine. Microsoft has been busy!


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 either typeOf or the GetType() method, as shown in Example 18-4.

Example 18-4. Reflecting on a type
#region Using directives using System; using System.Collections.Generic; using System.Reflection; using System.Text; #endregion namespace ReflectingOnAType {    public class Tester    {       public static void Main( )       {          // examine a type          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
#region Using directives using System; using System.Collections.Generic; using System.Reflection; using System.Text; #endregion namespace ReflectingOnMembersOfAType {    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:

System.Type GetType(System.String, Boolean, Boolean) is a Method System.Type[] GetExportedTypes() is a Method System.Reflection.Module GetModule(System.String) is a Method System.String get_FullName( ) 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();

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 BindingFlags values, including IgnoreCase, Instance, Public, Static, and so forth.


MemberFilter

A delegate (see Chapter 12) that filters the list of members in the MemberInfo array of objects. You use a Type.FilterName filter, which is a field of the Type class that filters on a name.


Object

A string value used by the filter. In this case you 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
#region Using directives using System; using System.Collections.Generic; using System.Reflection; using System.Text; #endregion namespace FindingParticularMembers {    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 GetType(System.String, Boolean, Boolean) is a Method System.Type[] GetExportedTypes( ) is a Method System.Reflection.Module GetModule(System.String) is a Method System.Reflection.AssemblyName[] GetReferencedAssemblies( ) is a Method Int64 GetHostContext( ) is a Method System.String GetLocation( ) is a Method System.String GetFullName( ) is a Method

18.2.4. Late Binding

Once you find 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 can, 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 to 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(), first get the Type information for the System.Math class:

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

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

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


CreateComInstanceFrom

Creates instances of COM objects.


CreateInstanceFrom

Creates 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

Creates 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() were 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 didn't take any values, you can still create the array, but you 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
#region Using directives using System; using System.Collections.Generic; using System.Reflection; using System.Text; #endregion namespace DynamicallyInvokingAMethod {    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 );       }    } } Output: The cosine of a 45 degree angle 0.707106781186548

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.



Programming C#(c) Building. NET Applications with C#
Programming C#: Building .NET Applications with C#
ISBN: 0596006993
EAN: 2147483647
Year: 2003
Pages: 180
Authors: Jesse Liberty

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net