[ Team LiB ] |
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 and System.TypedReference classes, 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 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 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. 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 assemblynamespace 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 TypeYou 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 typenamespace 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 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 typenamespace 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 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(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 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 membersnamespace 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 BindingOnce 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.
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:
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);
Example 18-7 illustrates dynamically calling the Cos( ) method. Example 18-7. Dynamically invoking a methodnamespace 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 ] |