Reflection: Inspection of a Type's MetadataThe act of inspecting a type's metadata at runtime is called reflection , and it is a fundamental facility of the CLR. In fact, reflection is so fundamental that the type system's base class, System.Object , has a method GetType specifically designed to facilitate this inspection. The GetType method returns an object of type Type ”a slightly unfortunate choice, as this overloading of the word type can sometimes prove confusing. To distinguish between the two usages, in this chapter the term type, with a lowercase t, normally means any type in the CLR, such as the Object or String type. The term Type , with a capital T, refers to the Type class provided by the Base Framework. The GetType() method returns a singleton Type object for the type on which it was invoked. The Type class provides a means for developers to access the metadata of the type it describes. Reflection ClassesA number of classes are used in the .NET Framework's reflection facilities. This section describes the architecture of these classes and demonstrates how developers use these classes. Note that a full and complete description of all of the functionality of the reflection classes is beyond the scope of this book. Instead, we provide a generalized overview of the structure of these classes; users should then be able to extrapolate the general usage. The MemberInfo and Type classes are the foundation for the reflection facilities. The Type class provides several methods and properties to permit inspection of a type's metadata. It inherits from the MemberInfo class, which in turn inherits from Object . Figure 3.1 shows the relationship between fundamental classes in the reflection hierarchy. Figure 3.1. The reflection class hierarchy
MemberInfo ClassThe MemberInfo class serves as a base class for many of the reflection " Info " classes ”that is, classes whose names end in the word " Info " and are extensively used in the CLR's reflection services. [1] The MemberInfo class is abstract. Subtypes of MemberInfo include the following:
MethodBase serves as the abstract base class for the following classes:
One other class is also used by many of the reflection " Info " classes:
This common base, MemberInfo , provides a number of methods that can be invoked on all " Info " classes as well as an abstract base class that is used as the return type of many methods. An example of the useful functionality specified by this class is the GetCustomAttributes method of the MemberInfo class. All members of a type can be annotated with custom attributes, which consist of user -defined metadata annotations. (Custom attributes are explained in detail later in this chapter.) The GetCustomAttributes method ensures that developers can access the custom attributes on any subclass of MemberInfo regardless of the subtype with which a developer is dealing. (The list of custom attributes on a member may, of course, be empty.) Instances of most classes derived from MemberInfo are provided as singletons. For example, when a Type object is required for a type, it will be created once and that same object will be reused whenever a Type object is required for that specific type. The same situation applies to many of the other reflection classes. Thus, if an EventInfo object is created for an event on a certain type, then that object is reused whenever information is needed for that event on the particular type. Another example of the usefulness of the MemberInfo class as a base class involves the GetMembers method defined on the Type type. It returns an array of MemberInfo objects, so that the array can hold instances of any derived class. As a consequence, a single array can hold Info classes for properties, events, methods, and fields. Type ClassLike MemberInfo , the Type class is an abstract class. Like the String class described in Chapter 2, it provides a number of methods ”far too many to cover individually here. To simplify the description of the functionality offered by the members of the Type class, the methods and properties can be divided into groups based on the services they provide. From a very general viewpoint, the methods could be categorized as follows :
Get MethodsA number of methods on the Type class are prefixed with the word Get . As this naming convention suggests, these methods return information about an aspect of the type. For example, the GetMethod method returns a MethodInfo object that describes a method defined by the type. GetMethod requires a parameter of type String that supplies the name of the method needed. By comparison, the GetMethods method does not require a string to designate which method is needed; it returns an array of MethodInfo objects that represents all of the publicly available methods for a type. Is Methods and PropertiesA number of properties on the Type class return a Boolean value that describes a single aspect of the type. For example, the IsAutoLay property returns true or false to indicate whether the execution engine automatically lays out the type at runtime. Sometimes symmetrically related properties are available. For example, the IsExplicitLayout , IsLayoutSequential , and IsAutoLayout properties indicate whether the developer has specified a specific memory layout for the class. One and only one of these properties can be true for any type. PropertiesEach type has a number of properties, one variety of which is the Is properties described previously. Other properties include the required properties AssemblyQualifiedName , BaseType , and FullName . Every type must have these properties, which have only a single value. Static FieldsStatic fields represent values that are shared by all types in the CLR. For example, the EmptyTypes field represents an empty array of Type objects. For a complete list of the members of the Type class, consult the current definition of Type in the SDK. Table 3.1 briefly describes the functionality of the Type class's methods. Example: Using ReflectionIt is often much easier to understand how the classes function together by working through a simple example. The program fragment in Listing 3.1 builds on many of the concepts already discussed. The code demonstrates the use of the Type object for the Object class. This program accesses the Type object for the System.Object class. It then writes a number of the type's properties to the console as well as a string representing each member that the type has. Listing 3.1 Use of the Type object for the Object classusing System; using System.Reflection; namespace ReflectionSample { class ObjectSample { static void Main(string[] args) { Type t = Type.GetType("System.Object"); Console.WriteLine(t.FullName); Console.WriteLine(t.AssemblyQualifiedName); Console.WriteLine("GUID: " + t.GUID); Console.WriteLine("IsAbstract: " + t.IsAbstract); Console.WriteLine("IsClass: " + t.IsClass); Console.WriteLine("IsInterface: " + t.IsInterface); Console.WriteLine("IsSealed: " + t.IsSealed); MemberInfo[] members = t.GetMembers(); foreach(MemberInfo m in members) Console.WriteLine("\t{0}",m); } } } Table 3.1. Type Class Methods
You are already familiar with the Object class. The code in Listing 3.1 uses the Type class's static GetType method to return a reference to the singleton Type object for the Object class. As the sample programs in this book are written in C#, you could use the C# keyword typeof to access the Type object for a type. The static method is used here because not all languages have a typeof keyword but all languages should be able to call the GetType method from the Framework Class Library. Next, the program displays some properties, such as the type's full name, its assembly qualified name, [2] and its GUID. A GUID is stored with the class for interoperability with COM but is not a fundamental aspect of identifying a type in the CLR as it is in COM.
Next, the program displays some information about the type: Is it abstract? Is it a class? Is it an interface? Is it sealed? Finally, the GetMembers method is called to retrieve an array of MemberInfo -derived classes. Each element in the array represents one member of the type. The program iterates through the array and prints out a string representation for each element. Listing 3.1 produces the following output: System.Object System.Object, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 GUID: 81c5fe01-027c-3e1c-98d5-da9c9862aa21 IsAbstract: False IsClass: True IsInterface: False IsSealed: False Int32 GetHashCode() Boolean Equals(System.Object) System.String ToString() Boolean Equals(System.Object, System.Object) Boolean ReferenceEquals(System.Object, System.Object) System.Type GetType() Void .ctor() Note that if you run the program fragment in Listing 3.1, you may get slightly different output. In particular, the version of the .NET Framework that you are using may differ from that shown here. Essentially, however, the output will provide the same information. Listing 3.1 clearly demonstrates how simple the reflection facilities are. To reinforce a recurring theme, we do not know in which language Object was written, but this fact is of no consequence. You could also write the sample program in any CLR language and retrieve and display the same information. Listing 3.2 extends Listing 3.1 by displaying more metadata, but this time about the String class. This code reveals its constructors and the name of the parameters. Listing 3.2 Use of the Type object for the String classusing System; using System.Reflection; namespace StringSampleExtended { class Sample { static void Main(string[] args) { Type t = Type.GetType("System.String"); Console.WriteLine(t.FullName); Console.WriteLine(t.AssemblyQualifiedName); Console.WriteLine("GUID: " + t.GUID); Console.WriteLine("IsAbstract: " + t.IsAbstract); Console.WriteLine("IsClass: " + t.IsClass); Console.WriteLine("IsInterface: " + t.IsInterface); Console.WriteLine("IsSealed: " + t.IsSealed); ConstructorInfo[] constructorArray = t.GetConstructors(); Console.WriteLine("Constructors: " + constructorArray.Length); foreach(ConstructorInfo c in constructorArray) { Console.WriteLine("\t{0}", c); ParameterInfo[] parameterArray = c.GetParameters(); foreach(ParameterInfo p in parameterArray ) Console.WriteLine("\t\t{0}", p.Name); } } } } The basic structure of Listing 3.2 is the same as that for Listing 3.1, the example using the Object class. The first really different line of code is the one that uses the String 's Type object to retrieve an array of ConstructorInfo objects. The number of constructors, represented by the length of the array, is written to the console. Next, the program loops through the array of ConstructorInfo objects using the C# foreach looping construct. The first line inside the loop implicitly uses the ToString method on each ConstructorInfo object to output a string representing itself on the console. Then, for every constructor, the program retrieves an array of ParameterInfo objects. It uses the ParameterInfo object's Name property to display its name. Listing 3.2 produces the following output: System.String System.String, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 GUID: 296afbff-1b0b-3ff5-9d6c-4e7e599f8b57 IsAbstract: False IsClass: True IsInterface: False IsSealed: True Constructors: 8 Void .ctor(Char*) value Void .ctor(Char*, Int32, Int32) value startIndex length Void .ctor(SByte*) value Void .ctor(SByte*, Int32, Int32) value startIndex length Void .ctor(SByte*, Int32, Int32, System.Text.Encoding) value startIndex length enc Void .ctor(Char[], Int32, Int32) value startIndex length Void .ctor(Char[]) value Void .ctor(Char, Int32) c count Note that String resides in the same assembly as Object ”namely, mscorlib ”and that String is a sealed class ”that is, you cannot create subtypes of String . All instance constructors share the same name, .ctor . Although the output from Listing 3.2 does not include one, the single class constructor is called .cctor . A type can have only one constructor because the constructor for the class is automatically called at runtime and no provision has been made for developers to supply an argument list to that call. As a consequence, you cannot overload the class constructor signature. Listings 3.1 and 3.2 demonstrate only a fraction of the functionality available with the reflection facilities for ascertaining metadata on a type. Example: Use of Type as an Abstract TypeIf, as mentioned previously, Type is an abstract type, then exactly what type of object is returned when you invoke the GetType method? The simple answer is found by invoking the GetType method on the Type object returned by GetType . Listing 3.3 does exactly that for the String 's Type object. Listing 3.3 Invoking the GetType method on the Type object returned by GetTypeusing System; namespace GetTypeGetType { class Sample { static void Main(string[] args) { Type t = Type.GetType("System.String"); t = t.GetType(); Console.WriteLine(t.FullName); Console.WriteLine(t.AssemblyQualifiedName); Console.WriteLine("Base Type: " + t.BaseType); Console.WriteLine("Attributes: " + t.Attributes); Console.WriteLine("Module: " + t.Module); } } } Listing 3.3 produces the following output: System.RuntimeType System.RuntimeType, mscorlib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Base Type: System.Type Attributes: AutoLayout, AnsiClass, NotPublic, Sealed, Serializable, BeforeFieldInit Module: CommonLanguageRuntimeLibrary This output demonstrates some other aspects of the CLR, such as versioning and public keys, which are covered later in this book. |