Reflection

 
Chapter 5 - C# and the Base Classes
bySimon Robinsonet al.
Wrox Press 2002
  

Reflection is a generic term that covers the various .NET base classes that allow you to find out information about the types in your programs or in other assemblies, and also to read other metadata from assembly manifests . Most of these classes are in the namespace System.Reflection , and there are a huge number of classes in this namespace. We don't have space here to touch on more than a fraction of what you can do with the reflection classes, but we will give you enough to start you off.

In this section, we will start by having a closer look at the System.Type class, which lets you access information concerning the definition of any given data type. We will next have a brief look at the System.Reflection.Assembly class, which you can use to access information about a given assembly, or to load that assembly into your program. Finally, we will put everything in this section and the previous section about custom attributes together, by completing the WhatsNewAttributes sample.

The System.Type Class

We have already used the Type class on a number of occasions through this book, but so far only to retrieve the name of a type:

   Type t = typeof(double)   

In fact, although we loosely refer to Type as a class, it is in reality an abstract base class. Whenever you instantiate a Type object, you are actually instantiating a derived class of Type . Type has one derived class corresponding to each actual data type, though in general the derived classes simply provide different overloads of the various Type methods and properties that return the correct data for the corresponding data type. They do not generally add new methods or properties. In general, there are three common ways of obtaining a Type reference that refers to any given type:

  • Use the C# typeof operator as illustrated above. This operator takes the name of the type (not in quote marks however) as a parameter.

  • Use the GetType() method, which all classes inherit from System.Object :

       double d = 10;     Type t = d.GetType();   

    GetType() is called against a variable, rather than taking the name of a type. Note, however, that the Type object returned is still associated with only that data type. It does not contain any information that relates to that instance of the type. However, this method can be useful if you have a reference to an object, but are not sure what class that object is actually an instance of.

  • You can also call the static method of the Type class, GetType() :

       Type t = Type.GetType("System.Double");   

Type is really the gateway to much of the reflection technology. It implements a huge number of methods and properties again, far too many to give a comprehensive list here, but the following sub-sections should give you some idea of the kind of things you can do with this class.

Note that the available properties are all read-only; you use Type to find out about the data type you can't use it to make any modifications to the type!

Type Properties

The properties implemented by Type can be split into three categories:

  1. There are a number of properties that retrieve the strings containing various names associated with the class:

    Property

    Returns

    Name

    The name of the data type

    FullName

    The fully qualified name of the data type (including the namespace name)

    Namespace

    The name of the namespace in which the data type is defined

  2. It is also possible to retrieve references to further type objects that represent related classes:

    Property

    Returns Type Reference Corresponding To

    BaseType

    Immediate base type of this type

    UnderlyingSystemType

    The type that this type maps to in the .NET runtime (recall that certain .NET base types actually map to specific predefined types recognized by IL)

  3. There are a number of Boolean properties that indicate whether or not this type is, for example, a class, an enum , and so on. These properties include: IsAbstract , IsArray , IsClass , IsEnum , IsInterface , IsPointer , IsPrimitive (one of the predefined primitive data types), IsPublic , IsSealed , and IsValueType .

    For example, using a primitive data type:

       Type intType = typeof(int);     Console.WriteLine(intType.IsAbstract);     // writes false     Console.WriteLine(intType.IsClass);        // writes false     Console.WriteLine(intType.IsEnum);         // writes false     Console.WriteLine(intType.IsPrimitive);    // writes true     Console.WriteLine(intType.IsValueType);    // writes true   

    Or using our Vector class:

       Type intType = typeof(Vector);     Console.WriteLine(intType.IsAbstract);     // writes false     Console.WriteLine(intType.IsClass);        // writes true     Console.WriteLine(intType.IsEnum);         // writes false     Console.WriteLine(intType.IsPrimitive);    // writes false     Console.WriteLine(intType.IsValueType);    // writes false   

    You can also retrieve a reference to the assembly that the type is defined in. This is returned as a reference to an instance of the System.Reflection.Assembly class, which we will examine soon:

       Type t = typeof (Vector);     Assembly containingAssembly = new Assembly(t);   

Methods

Most of the methods of System.Type are used to obtain details of the members of the corresponding data type the constructors, properties, methods, events, and so on. There are quite a large number of methods, but they all follow the same pattern. For example, there are two methods that retrieve details of the methods of the data type: GetMethod() and GetMethods() . GetMethod() returns a reference to a System.Reflection.MethodInfo object, which contains details of a method. GetMethods() returns an array of such references. The difference is that GetMethods() returns details of all the methods, while GetMethod() returns details of just one method with a specified parameter list. Both methods have overloads that take an extra parameter, a BindingFlags enumerated value that indicates which members should be returned for example, whether to return public members, instance members, static members and so on.

So for example, the simplest overload of GetMethods() takes no parameters and returns details of all the public methods of the data type:

   Type t = typeof(double);     MethodInfo [] methods = t.GetMethods();     foreach (MethodInfo nextMethod in methods)     {     // etc.   

Following the same pattern are the following member methods of Type :

Type of object returned

Methods (the method with the plural name returns an array)

ConstructorInfo

GetConstructor() , GetConstructors()

EventInfo

GetEvent() , GetEvents()

FieldInfo

GetField() , GetFields()

InterfaceInfo

GetInterface() , GetInterfaces()

MemberInfo

GetMember() , GetMembers()

MethodInfo

GetMethod() , GetMethods()

PropertyInfo

GetProperty() , GetProperties()

The GetMember() and GetMembers() methods return details of any or all members of the data type, irrespective of whether these members are constructors, properties, methods, and so on. Finally, note that it is possible to invoke members either by calling the InvokeMember() method of Type , or by calling the Invoke() method of the MethodInfo , PropertyInfo, and the other classes.

The TypeView Example

We will now demonstrate some of the features of the Type class by writing a short example, TypeView , which we can use to list the members of a data type. We will demonstrate use of TypeView for a double, but we can swap to any other data type just by changing one line of the code for the sample. TypeView displays far more information than can be displayed in a console window, so we're going to take a break from our normal practice and display the output in a message box. Running TypeView for a double produces these results:

click to expand

The message box displays the name, full name, and namespace of the data type as well as the name of the underlying type and the base type. Then, it simply iterates through all the public instance members of the data type, displaying for each member the declaring type, the type of member (method, field, and so on) and the name of the member. The declaring type is the name of the class that actually declares the type member (in other words, System.Double if it is defined or overridden in System.Double , or the name of the relevant base type if the member is simply inherited from some base class).

TypeView does not display signatures of methods because we are retrieving details of all public instance members through MemberInfo objects, and information about parameters is not available through a MemberInfo object. In order to retrieve that information, we would need references to MethodInfo and other more specific objects, which means we would need to separately obtain details of each type of member.

TypeView does display details of all public instance members, but it happens that for doubles, the only ones defined are fields and methods. We will compile TypeView as a console application there is no problem with displaying a message box from a console application. However, the fact that we are using a message box means that we need to reference the base class assembly System.Windows.Forms.dll , which contains the classes in the System.Windows.Forms namespace in which the MessageBox class that we will need is defined. The code for TypeView is as follows . To start with, we need to add a couple of using statements:

 using System;   using System.Text;     using System.Windows.Forms;     using System.Reflection;   

We need System.Text because we will be using a StringBuilder object to build up the text to be displayed in the message box, and System.Windows.Forms for the message box itself. The entire code is in one class, MainClass , which has a couple of static methods, and one static field, a StringBuilder instance called OutputText, which will be used to build up the text to be displayed in the message box. The main method and class declaration look like this:

   class MainClass     {     static void Main()     {     // modify this line to retrieve details of any     // other data type     Type t = typeof(double);         AnalyzeType(t);     MessageBox.Show(OutputText.ToString(), "Analysis of type "     + t.Name);     Console.ReadLine();     }   

The Main() method implementation starts by declaring a Type object to represent our chosen data type. We then call a method, AnalyzeType() , which extracts the information from the Type object and uses it to build up the output text. Finally, we show the output in a message box. We have not encountered the MessageBox class before, but using it is fairly intuitive. We just call its static Show() method, passing it two strings, which will respectively be the text in the box and the caption. AnalyzeType() is where the bulk of the work is done:

   static void AnalyzeType(Type t)     {     AddToOutput("Type Name:  " + t.Name);     AddToOutput("Full Name:  " + t.FullName);     AddToOutput("Namespace:  " + t.Namespace);     Type tBase = t.BaseType;     if (tBase != null)     AddToOutput("Base Type:" + tBase.Name);     Type tUnderlyingSystem = t.UnderlyingSystemType;     if (tUnderlyingSystem != null)     AddToOutput("UnderlyingSystem Type:" + tUnderlyingSystem.Name);     AddToOutput("\nPUBLIC MEMBERS:");     MemberInfo [] Members = t.GetMembers();     foreach (MemberInfo NextMember in Members)     {     AddToOutput(NextMember.DeclaringType + " " +     NextMember.MemberType + " " + NextMember.Name);     }     }   

We implement this method by simply calling various properties of the Type object to get the information we need concerning the names, then call the GetMembers() method to get an array of MemberInfo objects that we can use to display the details of each method. Note that we use a helper method, AddToOutput() , to build up the text to be displayed in the message box:

   static void AddToOutput(string Text)     {     OutputText.Append("\n" + Text);     }   

The Assembly Class

The Assembly class is defined in the System.Reflection namespace, and allows you access to the metadata for a given assembly. It also contains methods to allow you to execute an assembly, assuming the assembly is an executable. Like the Type class, it contains a very large number of methods and properties too many for us to cover here. Instead, we will confine ourselves to covering those methods and properties that you need to get started, and which we will use to complete the WhatsNewAttributes sample.

Before you can do anything with an Assembly instance, you need to load the corresponding assembly into the running process. You can do this with either of the static members Assembly.Load() and Assembly.LoadFrom() . The difference between these methods is that Load() takes the name of the assembly, which must be an assembly that is already referenced from the currently executing assembly (in other words, it must be an assembly that you referenced when you were first compiling the project), while LoadFrom() takes the path name of an assembly, which can be any assembly that is present on your file system:

   Assembly assembly1 = Assembly.Load("SomeAssembly");     Assembly assembly2 = Assembly.LoadFrom     (@"C:\My Projects\GroovySoftware\SomeOtherAssembly");   

There are a number of other overloads of both methods, which supply additional security information. Once you have loaded an assembly, you can use various properties on it to find out, for example, its full name:

   string name = assembly1.FullName;   

Finding Out About Types Defined in an Assembly

One nice feature of the Assembly class is it allows you to very conveniently obtain details of all the types that have been defined in the corresponding assembly. You simply call the Assembly.GetTypes() method, which returns an array of System.Type references containing details of all the types. You can then manipulate these Type references just as you would with a Type object obtained from the C# typeof operator, or from Object.GetType() :

   Type[] types = theAssembly.GetTypes();     foreach(Type definedType in types)     DoSomethingWith(definedType);   

Finding Out About Custom Attributes

The methods you use to find out about what custom attributes are defined on an assembly or type depend on what type of object the type is attached to. If you want to find out what custom attributes are attached to an assembly as a whole, you need to call a static method of the Attribute class, GetCustomAttributes() , passing in a reference to the assembly:

   Attribute [] definedAttributes =     Attribute.GetCustomAttributes(assembly1);     // assembly1 is an Assembly object   

This is actually quite significant. You may have wondered why, when we defined custom attributes, we had to go to all the trouble of actually writing classes for them, and why Microsoft hadn't come up with some simpler syntax. Well, the answer is here. The custom attributes do genuinely exist as objects, and once an assembly is loaded you can read these attribute objects in, examine their properties, and call their methods.

GetCustomAttributes() , as used to get assembly attributes, has a couple of overloads: if you call it without specifying any parameters other than a reference to the assembly then it will simply return all the custom attributes defined for that assembly. You can also call it by specifying a second parameter, which is a Type object that indicates the attribute class. In this case GetCustomAttributes() returns an array consisting of all the attributes present that are of that class. We will use this overload in the WhatsNewAttributes example in order to find out whether the SupportsWhatsNew attribute is present in the assembly. To do this, we called GetCustomAttributes() , passing in a reference to the assembly, and the type of the SupportWhatsNewAttribute attribute. If this attribute is present, we get an array containing all instances of it. If there are no instances of it defined in the assembly, then we return null :

   Attribute supportsAttribute =     Attribute.GetCustomAttributes(assembly1,     typeof(SupportsWhatsNewAttribute));   

Note that all attributes are retrieved as plain Attribute references. If you want to call any of the methods or properties you defined for your custom attributes, then you will need to cast these references explicitly to the relevant custom attribute classes. You can obtain details of custom attributes that are attached to a given data type by calling another overload of Assembly.GetCustomAttributes() , this time passing a Type reference that describes the type for which you want to retrieve any attached attributes. On the other hand, if you want to obtain attributes that are attached to methods, constructors, fields, and so on, then you will need to call a GetCustomAttributes() method that is a member of one of the classes MethodInfo , ConstructorInfo , FieldInfo , and so on. That is outside the scope of this chapter.

Completing the WhatsNewAttributes Sample

We now have enough information to complete the WhatsNewAttributes sample by writing the sourcecode for the final assembly in the sample, the LookUpWhatsNew assembly. This part of the application is a console application. However, it needs to reference both of the other assemblies. Although this is going to be a command-line application, we will follow the previous TypeView sample in actually displaying our results in a message box, since there is again going to be rather a lot of text output far too much to show in a console window screenshot.

The file is called LookUpWhatsNew.cs , and the command to compile it is

  csc /reference:WhatsNewAttributes.dll /reference:VectorClass.dll LookUpWhatsNew.cs  

In the sourcecode for this file, we first indicate the namespaces we wish to infer . System.Text is there because we need to use a StringBuilder object again:

   using System;     using System.Reflection;     using System.Windows.Forms;     using System.Text;     using Wrox.ProCSharp.VectorClass;     using Wrox.ProCSharp.WhatsNewAttributes;   namespace Wrox.ProCSharp.LookUpWhatsNew { 

Next, the class that will contain the main program entry point as well as the other methods, WhatsNewChecker . All the methods we define will be in this class, which will also have two static fields. outputText contains the text as we build it up in preparation for writing it to the message box. backDateTo stores the date we have selected all modifications made since this date will be displayed. Normally, we would display a dialog box inviting the user to pick this date, but I don't want to get sidetracked into that kind of code (besides, we haven't reached the Windows Forms chapter yet, so we don't yet know how to display a dialog box other than a simple message box!). For this reason, I have initialized backDateTo to a hard-coded date of 1 Feb 2002. You can easily change this date if you want when you download the code:

   class WhatsNewChecker     {     static StringBuilder outputText = new StringBuilder(1000);     static DateTime backDateTo = new DateTime(2002, 2, 1);         static void Main()     {     Assembly theAssembly = Assembly.Load("VectorClass");     Attribute supportsAttribute =     Attribute.GetCustomAttribute(     theAssembly, typeof(SupportsWhatsNewAttribute));     string Name = theAssembly.FullName;     AddToMessage("Assembly: " + Name);     if (supportsAttribute == null)     {     AddToMessage(     "This assembly does not support WhatsNew attributes");     return;     }     else     AddToMessage("Defined Types:");     Type[] types = theAssembly.GetTypes();     foreach(Type definedType in types)     DisplayTypeInfo(theAssembly, definedType);     MessageBox.Show(outputText.ToString(),     "What\'s New since " + backDateTo.ToLongDateString());     Console.ReadLine();     }   

The Main() method first loads the VectorClass assembly, and verifies that it is indeed marked with the SupportsWhatsNew attribute. It will do so, as we have only recently compiled that assembly with that attribute in, but this is a check that would be worth making if, more realistically , the user was given a choice of what assembly to check.

Assuming all is well, we use the Assembly.GetTypes() method to get an array of all the types defined in this assembly, and then loop through them. For each one, we call a method that we have written, DisplayTypeInfo() , which will add the relevant text, including details of any instances of LastModifiedAttribute , to the outputText field. Finally, we show the message box with the complete text. The DisplayTypeInfo() method looks like this:

   static void DisplayTypeInfo(Assembly theAssembly, Type type)     {     // make sure we only pick out classes     if (!(type.IsClass))     return;     AddToMessage("\nclass " + type.Name);     Attribute [] attribs = Attribute.GetCustomAttributes(type);     if (attribs.Length == 0)     AddToMessage("No changes to this class\n");     else     foreach (Attribute attrib in attribs)     WriteAttributeInfo(attrib);         MethodInfo [] methods = type.GetMethods();     AddToMessage("CHANGES TO METHODS OF THIS CLASS:");     foreach (MethodInfo nextMethod in methods)     {     object [] attribs2 =     nextMethod.GetCustomAttributes(     typeof(LastModifiedAttribute), false);     if (attribs2 != null)     {     AddToMessage(     nextMethod.ReturnType + " " + nextMethod.Name + "()");     foreach (Attribute nextAttrib in attribs2)     WriteAttributeInfo(nextAttrib);     }     }     }   

Notice that the first thing we do in this method is check whether the Type reference we have been passed actually represents a class. Since, in order to keep things simple, we have specified that the LastModified attribute can only be applied to classes or member methods, we will be wasting our time doing any processing if the item is not a class (it might in principle be a class, delegate, or enum).

Next, we use the Attribute.GetCustomAttributes() method to find out if this class does have any LastModifiedAttribute instances attached to it. If it does, we add their details to the output text, using a helper method, WriteAttributeInfo() , which we will consider next.

Finally, we use the Type.GetMethods() method to iterate through all the member methods of this data type, and then basically do the same thing with each method; check if it has any LastModifiedAttribute instances attached to it, and display them using WriteAttributeInfo() if it has.

The next bit of code shows the WriteAttributeInfo() method, which is responsible for working out what text to display for a given LastModifiedAttribute instance. Note that this method is passed an Attribute reference, so it needs to cast this to a LastModifiedAttribute reference first. Once it has done that, it uses the properties that we originally defined for this attribute to retrieve its parameters. It checks that the date of the attribute is sufficiently recent before actually adding it to the text to be displayed:

   static void WriteAttributeInfo(Attribute attrib)     {     LastModifiedAttribute lastModifiedAttrib =     attrib as LastModifiedAttribute;     if (lastModifiedAttrib == null)     return;     // check that date is in range     DateTime modifiedDate = lastModifiedAttrib.DateModified;     if (modifiedDate < backDateTo)     return;     AddToMessage("  MODIFIED: " +     modifiedDate.ToLongDateString() + ":");     AddToMessage("    " + lastModifiedAttrib.Changes);     if (lastModifiedAttrib.Issues != null)     AddToMessage("    Outstanding issues:" +     lastModifiedAttrib.Issues);     AddToMessage("");     }   

Finally, here is the helper AddToMessage() method:

   static void AddToMessage(string message)     {     outputText.Append("\n" + message);     }   

Running this code produces these results:

click to expand

Notice that when we listed the types defined in the VectorClass assembly, we actually picked up two classes: Vector , and the embedded VectorEnumerator class that we added when we turned Vector into a collection earlier in the chapter. Also notice that since we hard-coded a backDateTo date of 1 Feb in this code, we have actually picked up the attributes that were dated 14 Feb (when we added the collection stuff), but not those dated 14 Jan (when we added the IFormattable interface).

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

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