18.2. ReflectionFor 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.
18.2.1. Viewing MetadataIn 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 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 reflectionpublic 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 DiscoveryYou 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.
18.2.3. Reflecting on a TypeYou 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 membersYou 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 methodsYou 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 membersFinally, 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:
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 BindingOnce 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.
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.
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);
Type[] paramTypes = new Type[0];
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. |