Reflection


Using reflection, it is possible to do the following.

  • Access the metadata for types within an assembly. This includes constructs such as the full type name, member names, and any attributes decorating a construct.

  • Dynamically invoke a type's member at runtime using the metadata, rather than a compile-time-defined binding.

Reflection is the process of examining the metadata within an assembly. Traditionally, when code compiles down to a machine language, all the metadata (such as type and method names) about the code is discarded. In contrast, when C# compiles into the CIL, it maintains most of the metadata about the code. Furthermore, using reflection, it is possible to enumerate through all the types within an assembly and search for those that match certain characteristics. You access a type's metadata through instances of System.Type, and this object includes methods for enumerating the type instance's members. Furthermore, it is possible to invoke those members on particular objects that are of the examined type.

The facility for reflection enables a host of new paradigms that are unavailable otherwise. For example, reflection enables you to enumerate over all the types within an assembly, along with their members, and in the process create stubs for documentation of the assembly API. You can then combine the metadata retrieved from reflection with the XML document created from XML comments (using the /doc switch) to create the API documentation. Similarly, programmers use reflection metadata to generate code for persisting (serializing) business objects into a database. It could also be used in a list control that displays a collection of objects. Given the collection, a list control could use reflection to iterate over all the properties of an object in the collection, defining a column within the list for each property. Furthermore, by invoking each property on each object, the list control could populate each row and column with the data contained in the object, even though the data type of the object is unknown at compile time.

XmlSerializer, ValueType, and DataBinder are a few of the classes in the framework that use reflection for portions of their implementation as well.

Accessing Metadata Using System.Type

The key to reading a type's metadata is to obtain an instance of System .Type that represents the target type instance. System.Type provides all the methods for retrieving the information about a type. You can use it to answer questions such as the following.

  • What is the type's name (Type.Name)?

  • Is the type public (Type.IsPublic)?

  • What is the type's base type (Type.BaseType)?

  • Does the type support any interfaces (Type.GetInterfaces())?

  • Which assembly is the type defined in (Type.Assembly)?

  • What are a type's properties, methods, fields, and so on (Type.GetProperties(), Type.GetMethods(), Type.GetFields(), etc.)?

  • What attributes decorate a type (Type.GetCustomAttributes())?

There are more such members, but in summary, they all provide information about a particular type. The key, obviously, is to obtain a reference to a type's Type object. The two primary ways to do this are through object.GetType() and typeof().

GetType()

object includes a GetType() member, and therefore, all types include this function. You call GetType() to retrieve an instance of System.Type corresponding to the original object. Listing 14.1 demonstrates this, using a Type instance from DateTime. Output 14.1 shows the results.

Listing 14.1. Using Type.GetProperties() to Obtain an Object's Public Properties

 DateTime dateTime = new DateTime();      //  Type type = dateTime.GetType(); foreach (      System.Reflection.PropertyInfo property in         type.GetProperties()) {         Console.WriteLine(property.Name); } 

Output 14.1.

 Date Day DayOfWeek DayOfYear Hour Kind Millisecond Minute Month Now UtcNow Second Ticks TimeOfDay Today Year 

After calling GetType(), you iterate over each System.Reflection.PropertyInfo instance returned from Type.GetProperties() and display the property names. The key to calling GetType() is that you must have an object instance. However, sometimes no such instance is available. Static classes, for example, cannot be instantiated, so there is no way to call GetType().

typeof()

Another way to retrieve a Type object is with the typeof expression. typeof binds at compile time to a particular Type instance, and it takes a type directly as a parameter. Listing 14.2 demonstrates the use of typeof with Enum.Parse().

Listing 14.2. Using typeof() to Create a System.Type Instance

 using System.Diagnostics;      // ...         ThreadPriorityLevel priority;         priority = (ThreadPriorityLevel)Enum.Parse(               typeof(ThreadPriorityLevel), "Idle");     // ... 

Enum.Parse() takes a Type object identifying an enum and then converts a string to the specific enum value. In this case, it converts "Idle" to System.Diagnostics.ThreadPriorityLevel.Idle.

Member Invocation

The possibilities with reflection don't stop with retrieving the metadata. The next step is to take the metadata and dynamically invoke the members they reference. Consider the possibility of defining a class to represent an application's command line. The difficulty with a CommandLineInfo class such as this has to do with populating the class with the actual command-line data that started the application. However, using reflection, you can map the command-line options to property names and then dynamically set the properties at runtime. Listing 14.3 demonstrates this example.

Listing 14.3. Dynamically Invoking a Member

 using System; using System.Diagnostics; public partial class Program {   public static void Main(string[] args)   {       string errorMessage;       CommandLineInfo commandLine = new CommandLineInfo();       if (!CommandLineHandler.TryParse(           args, commandLine, out errorMessage))       {           Console.WriteLine(errorMessage);           DisplayHelp();       }       if (commandLine.Help)       {           DisplayHelp();       }       else       {           if (commandLine.Priority !=                 ProcessPriorityClass.Normal)               {                 // Change thread priority               }              }       // ...   }   private static void DisplayHelp()   {       // Display the command-line help.   } } using System; using System.Diagnostics; public partial class Program {   private class CommandLineInfo   {       public bool Help       {           get { return _Help; }           set { _Help = value; }       }       private bool _Help;       public string Out       {           get { return _Out; }           set { _Out = value; }       }       private string _Out;       public ProcessPriorityClass Priority       {           get { return _Priority;}           set { _Priority = value;}       }       private ProcessPriorityClass_Priority =           ProcessPriorityClass.Normal;   } } using System; using System.Diagnostics; using System.Reflection; public class CommandLineHandler {   public static void Parse(string[] args, object commandLine)   {       string errorMessage;       if (!TryParse(args, commandLine, out errorMessage))       {           throw new ApplicationException(errorMessage);       }       }   public static bool TryParse(string[] args, object commandLine,       out string errorMessage)   {       bool success = false;       errorMessage = null;       foreach (string arg in args)       {           string option;           if (arg[0] == '/' || arg[0] =='-')           {                   string[] optionParts = arg.Split(                   new char[] {'' }, 2);              // Remove the slash|dash               option = optionParts[0].Remove(0, 1);                PropertyInfo property=                                                           commandLine.GetType().GetProperty(option,                                        BindingFlags.IgnoreCase|                                                     BindingFlags.Instance |                                                      BindingFlags.Public);                                                if (property != null)                                                        {                                                                               if (property.PropertyType == typeof(bool))                                       {                                                                               // Last parameters for handling indexers                                     property.SetValue(                                                               commandLine, true,null);                                                 success = true;                                                           }                                                                            else if (                                                                       property.PropertyType == typeof(string))                               {                                                                                property.SetValue(                                                               commandLine, optionParts[1], null);                                      success = true;                                                          }                                                                            else if (property.PropertyType.IsEnum)                                       {                                                                               try                                                                          {                                                                                property.SetValue(commandLine,                                                   Enum.Parse(                                                                       typeof(ProcessPriorityClass),                                                optionParts[1], true),                                                  null);                                                                   success = true;                                                          }                                                                            catch (ArgumentException )                      {                           success = false;                           errorMessage =                               string.Format(                                   "The option '{0}' is " +                                   "invalid for '{1}'",                               optionParts[1], option);                       }                   }                   else                   {                       success = false;                       errorMessage = string.Format(                           "Data type '{0}' on {1} is not"                           + " supported.",                          property.PropertyType.ToString(),                          commandLine.GetType().ToString());                   }               }               else               {                   success = false;                   errorMessage = string.Format(                       "Option '{0}' is not supported.",                       option);               }           }       }       return success;   } } 

Although Listing 14.3 is long, the code is relatively simple. Main() begins by instantiating a CommandLineInfo class. This type is defined specifically to contain the command-line data for this program. Each property corresponds to a command-line option for the program where the command line is as shown in Output 14.2.

Output 14.2.

 Compress.exe /Out<file name> /Help    /PriorityRealTime|High|AboveNormal||Normal|BelowNormal|Idle 

The CommandLineInfo object is passed to the CommandLineHandler's TRyParse() method. This method begins by enumerating through each option and separating out the option name (Help or Out, for example). Once the name is determined, the code reflects on the CommandLineInfo object, looking for an instance property with the same name. If the property is found, it assigns the property using a call to SetValue() and specifying the data corresponding to the property type. (For arguments, this call accepts the object on which to set the value, the new value, and an additional index parameter that is null unless the property is an indexer.) This listing handles three property types Boolean, string, and enum. In the case of enums, you parse the option value and assign the property the text's enum equivalent. Assuming the tryParse() call was successful, the method exits and the CommandLineInfo object is initialized with the data from the command line.

Interestingly, in spite of the fact that CommandLineInfo is a private class nested within Program, CommandLineHandler has no trouble reflecting over it and even invoking its members. In other words, reflection is able to circumvent accessibility rules as long as appropriate code access security (CAS) permissions are established. If, for example, Out was private, it would still be possible for the TRyParse() method to assign it a value. Because of this, it would be possible to move CommandLineHandler into a separate assembly and share it across multiple programs, each with their own CommandLineInfo class.

In this particular example, you invoke a member on CommandLineInfo using PropertyInfo.SetValue(). Not surprisingly, PropertyInfo also includes a GetValue() method for retrieving data from the property. For a method, however, there is a MethodInfo class with an Invoke() member. Both MethodInfo and PropertyInfo derive from MemberInfo (although indirectly), as shown in Figure 14.1.

The CAS permissions are set up to allow private member invocation in this case because the program runs from the local computer, and by default, locally installed programs are part of the trusted zone and have appropriate permissions granted. Programs run from a remote location will need to be explicitly granted such a right.

Figure 14.1. MemberInfo Derived Classes


Reflection on Generic Types

Just as you can use reflection on nongeneric types, the 2.0 framework has provisions for reflecting on generic types. Runtime reflection on generics determines whether a class or method contains a generic type, and any type parameters or arguments it may contain.

Determining the Type of Type Parameters

In the same way that you can use a typeof operator with nongeneric types to retrieve an instance of System.Type, you can use the typeof operator on type parameters in a generic type or generic method. Listing 14.4 applies the typeof operator to the type parameter in the Add method of a Stack class.

Listing 14.4. Declaring the Stack<T> Class

 public class Stack<T> {     ...     public void Add(T i)     {         ...         Type t = typeof(T);         ...     }     ... } 

Once you have an instance of the Type object for the type parameter, you may then use reflection on the type parameter itself to determine its behavior and tailor the Add method to the specific type more effectively.

Determining Whether a Class or Method Supports Generics

In the System.Type class for CLI 2.0, a handful of new methods determine whether a given type supports generic parameters and arguments. A generic argument is a type parameter supplied when a generic class is instantiated. You can determine whether a class or method contains generic parameters that have not yet been set by querying the Type.ContainsGenericParameters Boolean property, as demonstrated in Listing 14.5.

Listing 14.5. Reflection with Generics

 using System; public class Program {   static void Main()   {       Type type;       type = typeof(System.Nullable<>);      Console.WriteLine(type.ContainsGenericParameters);       Console.WriteLine(type.IsGenericType);       type = typeof(System.Nullable<DateTime>);      Console.WriteLine(!type.ContainsGenericParameters);       Console.WriteLine(type.IsGenericType);   } } 

Output 14.3 shows the results of Listing 14.5.

Output 14.3.

 True True True True 

Type.IsGenericType is a Boolean property that evaluates whether a type is generic.

Obtaining Type Parameters for a Generic Class or Method

You can obtain a list of generic arguments, or type parameters, from a generic class by calling the GetGenericArguments() method. The result is an array of System.Type instances that corresponds to the order in which they are declared as type parameters to the generic class. Listing 14.6 reflects into a generic type and obtains each type parameter. Output 14.4 shows the results.

Listing 14.6. Using Reflection with Generic Types

 using System; using System.Collections.Generic; Stack<int> s = new Stack<int>(); Type t = s.GetType(); foreach(Type types in t.GetGenericArguments()) {     System.Console.WriteLine(         "Type parameter: " + types.FullName); } 

Output 14.4.

 Type parameter System.Int32 




Essential C# 2.0
Essential C# 2.0
ISBN: 0321150775
EAN: 2147483647
Year: 2007
Pages: 185

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