Metadata and Reflection

for RuBoard

The Serialization example in Chapter 2 demonstrates how metadata makes possible many of the services of the Common Language Runtime. 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."

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

[2] 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 from which every CLR type inherits. After you get the Type associated with an object, you can use the reflection methods to get the related metadata.

The Reflection sample 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.cs .

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 .

 Assembly a = 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.583.29038, Culture=neutral,  PublicKeyToken=null found. 

One of the properties of the Assembly class is the CodeBase , discussed in Chapter 7 on Deployment. The security Evidence associated with this assembly is another property. The Evidence class is discussed in Chapter 12 on Security.

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

 MethodInfo entryMethodInfo = a.EntryPoint; 

Since this is a dynamic linked library (DLL), there is no entry. If it were an executable program we could use the Invoke method on the MethodInfo class to run the startup code in the assembly. [3]

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

The sample uses the Assembly's GetModules method to find the modules associated with this assembly. In this case we have only one, "customer.dll." We could next find the types associated with the module by using the GetTypes method on each Module instance returned by GetModules . Since there is only one module, we use the Assembly's GetTypes method to return an array of the assembly's types. Even if we had several modules, we would use Assembly.GetTypes if we did not care about the association of types and modules.

Type

The abstract class Type in the System namespace defines .NET types. Since there are no functions outside of classes or global variables in C#, [4] 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, values, arrays, and enumerations.

[4] Although they are permitted by the CTS and are legal in managed C++.

The Type class is also returned by the GetType method on the System.Object class and the static GetType method on the Type class itself. The latter method can be used with types that can be resolved statically.

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 , 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 BaseType System.Object :

 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 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 BaseType System.Object :

 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:

 for (int j = 0; j < methodInfo.Length; j++)  {    if (methodInfo[j].IsStatic)      Console.Write("          static ");    if (methodInfo[j].IsPublic)      Console.Write("          public ");    if (methodInfo[j].IsFamily)      Console.Write("          protected ");    if (methodInfo[j].IsAssembly)      Console.Write("          internal ");    if (methodInfo[j].IsPrivate)      Console.Write("          private ");    Console.Write("{0} ", methodInfo[j].ReturnType.Name);     Console.Write("{0}(", methodInfo[j].Name);    ParameterInfo[] paramInfo =  methodInfo[j].GetParameters();    long last = paramInfo.Length - 1;    for (int k = 0; k < paramInfo.Length; k++)    {      Console.Write("{0} {1}", paramInfo[k].ParameterType,        paramInfo[k].Name);      if (k != last)        Console.Write(", ");    }  Console.WriteLine(")");  } 

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 static, public, protected, internal, or private as well as the return type and method name. The method parameters are stored in a property array of type ParameterInfo .

This example should also make clear that types are assembly relative. The same type name and layout in two different assemblies is treated by the runtime as two separate types. When versioning assemblies, one has 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 Framework to provide services to your applications because it can understand the structure of your types.

Late Binding

Reflection can also be used to implement late binding. In late binding 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:

 Assembly a = Assembly.Load("Customer");  Type t = a.GetType("OI.NetCs.Acme.Customers");  MethodInfo mi = 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.

 Object customerInstance = Activator.CreateInstance(t); 

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

 object[] arguments = new Object[1];  int customerId = -1;  arguments[0] = customerId;  object returnType = 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() ==  Type.GetType("System.Collections.ArrayList"))  {    System.Collections.ArrayList arrayList =      (System.Collections.ArrayList)returnType;    for (int i = 0; i < arrayList.Count; i++)    {      Type itemType = arrayList[i].GetType();      FieldInfo[] fi = itemType.GetFields();      for (int j = 0; j < fi.Length; j++)      {        object fieldValue = fi[j].GetValue(arrayList[i]);        Console.Write("{0, -10} = {1, -15}", fi[j].Name,          fieldValue);      }    Console.WriteLine();    }  } 

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 in order to illustrate the main points. It should be clear, however, how to make this completely general.

You can go one step further and use the classes that emit metadata (in System.Reflection.Emit ). You can dynamically create an assembly and then load and run it.

for RuBoard


Application Development Using C# and .NET
Application Development Using C# and .NET
ISBN: 013093383X
EAN: 2147483647
Year: 2001
Pages: 158

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