Metadata and Reflection

Team-Fly    

 
Application Development Using Visual Basic and .NET
By Robert J. Oberg, Peter Thorsteinson, Dana L. Wyatt
Table of Contents
Chapter 10.  .NET Framework Classes


The Serialization example in Chapter 2 demonstrates how metadata makes many of the services of the Common Language Runtime possible. Many of the technologies we cover in the rest of the book rely on metadata, although we will not always stop and point this out.

Metadata is information about the assemblies, modules, and types that constitute .NET programs. If you have ever had to create IDL to generate a type library so that your C++ COM objects could be called by Visual Basic, or to create proxies and stubs, you will appreciate how useful metadata is and will be grateful that it comes "for free."

.NET compilers emit metadata, and the CLR, the .NET Framework, or your own programs can use this metadata. Since we want to give you an understanding of how metadata works we will focus our discussion on the use, not the creation, of metadata. Metadata is read using classes in the System.Reflection namespace. [1]

[1] There is a lower level set of unmanaged COM interfaces for accessing metadata, but we will not discuss them here. See "Metadata in .NET" by Matt Pietrek in the October 2000 MSDN Magazine .

When you load an assembly and its associated modules and types, the metadata is loaded along with it. You can then query the assembly to get those associated types. You can also call GetType on any CLR type and get its metadata. GetType is a method on System.Object , which every CLR type inherits from. After you get the Type associated with an object, you can use the reflection methods to get the related metadata.

The Reflection example program takes the case study's Customer assembly and prints out some of the metadata available. You should examine the output and source code as you read the next sections. You should especially compare the output of the program with the source code in the file customer.vb .

The program clearly shows that it is possible to retrieve all of the types in an assembly and reconstruct the structures, interfaces, properties, events, and methods associated with those types.

First we load the assembly into memory and write out its name .

 graphics/codeexample.gif Dim a As System.Reflection.Assembly = _    System.Reflection.Assembly.Load(assemblyName) Console.WriteLine("Assembly {0} found.", a.FullName) 

The output for this statement is appropriate for an unsigned assembly:

 Assembly Customer, Version=1.0.765.27723, Culture=neutral, PublicKeyToken=null found. 

One of the properties of the Assembly class is the CodeBase , discussed in Chapter 9. The security Evidence associated with this assembly is another property. Evidence is discussed in Chapter 16.

The following code tries to get the entry point for the assembly:

 Dim entryMethodInfo As MethodInfo = a.EntryPoint 

Since this is a component assembly, there is no program entry point (i.e., a DLL has no Main). If this was an executable program, we could use the Invoke method on the MethodInfo class to run the startup code in the assembly. [2]

[2] You can also load and execute the assembly from the AppDomain, as we discuss later in this chapter.

The sample uses the Assembly.GetModules method to find associated modules with this assembly. In this case we have only one, customer.dll . We could then find the types associated with the module. Instead, we use the Assembly.GetTypes method to return an array of the assembly's types.

Type

The abstract class Type in the System namespace defines .NET types. Since there are no functions outside of classes or global variables in .NET, getting all the types in an assembly will allow us to get all the metadata about the code in that assembly. Type represents all the types present in .NET: classes, interfaces, value types, arrays, and enumerations.

The Type class is also returned by the GetType method on the System.Object class and the shared GetType method on the Type class itself. The latter method can only be used with types that can be resolved statically (i.e., at compile time).

One of Type's properties is the assembly to which it belongs. You can get all the types in the containing assembly once you have the Type of one object. Type is an abstract class. At runtime, an instance of System.RuntimeType is returned.

If you examine the program's output, you will see that each type in the assembly, CustomerListItem , ICustomer , Customer , and Customers , is found, and its metadata is printed out. We can find out the standard attributes and the type from which the class derives for each type through the Attributes and BaseType properties.

The methods associated with the Type class enable you to get the associated fields, properties, interfaces, events, and methods. For example, the Customer type has no interfaces, properties, or events; four fields; three constructors; and the methods inherited from its base type System.Object:

 Type Customer found.     BaseType: Object     Attributes: AutoLayout, AnsiClass, NotPublic, Public,        BeforeFieldInit     GUID: 954e6037-f861-358c-aa1b-2aef8a0044b9     Interfaces:     Fields:        CustomerId        FirstName        LastName        EmailAddress     Properties:     Events:     Constructors:        Public .ctor(System.String first,                     System.String last,           System.String email)        Public .ctor()        Public .ctor(System.Int32 id)     Methods:        Public Int32 GetHashCode()        Public Boolean Equals(System.Object obj)        Public String ToString()        Public Boolean Equals(System.Object obj)        Public Int32 GetHashCode()        Public String ToString()        Public Type GetType() 

The type Customers inherits from one interface and has one constructor and four of its own methods in addition to the four it inherited from its base type System.Object:

 Type Customers found.     BaseType: Object     Attributes: AutoLayout, AnsiClass, NotPublic, Public     GUID: cd4071bc-aa3f-34c6-8e37-167925ad7ed3     Interfaces:        ICustomer     Fields:     Properties:     Events:     Constructors:        Public .ctor()     Methods:        Public Void ChangeEmailAddress(System.Int32 id,           System.String emailAddress)        Public ArrayList GetCustomer(System.Int32 id)        Public Void UnregisterCustomer(System.Int32 id)        Public Int32 RegisterCustomer(System.String           firstName, System.String lastName,           System.String emailAddress)        Public Int32 GetHashCode()        Public Boolean Equals(System.Object obj)        Public String ToString()        Public Type GetType() 

These were obtained with the GetInterfaces , GetFields , GetProperties , GetEvents , GetConstructors , and GetMethods methods on the Type class. Since an interface is a type, GetInterfaces returns an array of Types representing the interfaces inherited or implemented by the Type queried. Since fields, properties, events, and methods are not types, their accessor methods do not return Types . Each of their accessor methods returns an appropriate class: FieldInfo , PropertyInfo , EventInfo , ConstructorInfo , and MethodInfo . All these classes, as well as the Type class, inherit from the MemberInfo class that is the abstract base class for member metadata.

Let us examine some of the metadata associated with a class method. Using the reflection methods, we were able to reconstruct the signatures for all the classes and interfaces in the Customer assembly. Here is the output for the methods of the Customers class:

 Public Void ChangeEmailAddress(System.Int32 id,    System.String emailAddress) Public ArrayList GetCustomer(System.Int32 id) Public Void UnregisterCustomer(System.Int32 id) Public Int32 RegisterCustomer(System.String firstName,    System.String lastName, System.String emailAddress) Public Int32 GetHashCode() Public Boolean Equals(System.Object obj) Public String ToString() Public Type GetType() 

Here is the code from the example that produced the output:

 Dim methodInfo() As MethodInfo = t.GetMethods() For j = 0 To methodInfo.Length - 1    If methodInfo(j).IsStatic Then       Console.Write("          Shared ")    End If    If methodInfo(j).IsPublic Then       Console.Write("          public ")    End If    If methodInfo(j).IsFamily Then       Console.Write("          Protected ")    End If    If methodInfo(j).IsAssembly Then       Console.Write("          Internal ")    End If    If methodInfo(j).IsPrivate Then       Console.Write("          Private ")    End If    Console.Write("{0} ", _     methodInfo(j).ReturnType.Name)    Console.Write("{0}(", methodInfo(j).Name)    Dim paramInfo() As ParameterInfo = _       methodInfo(j).GetParameters()    Dim last As Long = paramInfo.Length - 1    Dim k As Integer    For k = 0 To paramInfo.Length - 1       Console.Write(_          "{0} {1}", _          paramInfo(k).ParameterType, _          paramInfo(k).Name)       If k <> last Then          Console.Write(", ")       End If    Next    Console.WriteLine(")") Next 

Except for the fact that a constructor does not have a return type, the exact same code reconstitutes the calling sequences for the class's constructors.

The MethodInfo class has properties that help us determine if the method is Shared , Public , Protected , Internal , or Private as well as determine the return type and method name. The method parameters are stored in property array of type ParameterInfo class.

This example should also make clear that types are assembly relative. The same type name and layout in two different assemblies are treated by the runtime as two separate types. When versioning assemblies, you have to be careful when mixing versioned types or the same types in two different assemblies.

All this metadata allows the Common Language Runtime and the .NET Framework to provide services to your applications, because they can understand the structure of your types.

Late Binding

Reflection can also be used to implement late binding. Late binding is where the method to be called is determined during execution rather than compilation. It is one example of how metadata can be used to provide functionality. As the previous example demonstrates, you can extract the signature of a method associated with a type. The MethodInfo object has all the needed metadata for a class method. The DynamicInvocation sample demonstrates a very simple example of late binding.

We dynamically load an assembly and get the metadata for a method of a particular type:

 graphics/codeexample.gif ' Load Customer assembly Dim a As System.Reflection.Assembly = _    System.Reflection.Assembly.Load("Customer") ' Get metadata for Customers class and one method Dim t As Type = _    a.GetType("Customer.OI.NetVB.Acme.Customers") Dim mi As MethodInfo = t.GetMethod("GetCustomer") 

Using the reflection classes, we could have made this completely dynamic by arbitrarily picking types, methods, and constructors from the Customer assembly using the techniques of the last example, but we wanted to keep the DynamicInvocation example simple.

The System namespace has an Activator class that has overloaded CreateInstance methods to create an instance of any .NET type using the appropriate constructor. The Activator class is discussed in this chapter's section on remoting. We invoke a constructor with no arguments to create an instance of the Customers object.

 ' Create Customers object, constructor takes no arguments Dim customerInstance As Object = _    Activator.CreateInstance(t) 

We then build an argument list and use the Invoke method of the MethodInfo instance to call the GetCustomer method.

 ' invoke the method Dim arguments() As Object = New Object(0) {} Dim customerId As Integer = -1 arguments(0) = customerId Dim returnType As Object = mi.Invoke(_    customerInstance, arguments) 

Using the reflection methods, we get the type information for each field in a return structure. Note the GetValue method that gets the data for a particular field in a structure. This is necessary because we cannot do pointer arithmetic to access an offset into a structure.

 If returnType.GetType() Is _  Type.GetType("System.Collections.ArrayList") Then    Dim arrayList As ArrayList = returnType    Dim i As Integer    For i = 0 To arrayList.Count - 1       Dim itemType As Type = arrayList(i).GetType()       Dim fi() As FieldInfo = itemType.GetFields()       Dim j As Integer       For j = 0 To fi.Length - 1          Dim fieldValue As Object = _           fi(j).GetValue(arrayList(i))          Console.Write(_           "{0, -10} = {1, -15}", fi(j).Name, fieldValue)       Next       Console.WriteLine()    Next End If 

This code did not use any specific objects or types from the Customer assembly. We did use some knowledge about the assembly to keep the code simple to illustrate the main points. It should be clear, however, how to make this completely general.

You can take this one step further and use the classes that emit metadata (in System.Reflection.Emit ). In this way, you can dynamically create an assembly, and then load and run it.


Team-Fly    
Top
 


Application Development Using Visual BasicR and .NET
Application Development Using Visual BasicR and .NET
ISBN: N/A
EAN: N/A
Year: 2002
Pages: 190

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