Reflection

Many of the services available in .NET and exposed via C# (such as late binding, serialization, remoting, attributes, etc.) depend on the presence of metadatas. Your own programs can also take advantage of this metadata, and even extend it with new information.

Manipulating existing types via their metadata is termed reflection and is done using a rich set of types in the System.Reflection namespace. Creating new types (and associated metadata) is termed Reflection.Emit, and is done via the types in the System.Reflection.Emit namespace. You can extend the metadata for existing types with custom attributes. For more information, see Section 3.11.

Type Hierarchy

Reflection involves traversing and manipulating an object model that represents an application, including all its compile-time and runtime elements. Consequently, it is important to understand the various logical units of a .NET application and their roles and relationships.

The fundamental units of an application are its types, which contain members and nested types. In addition to types, an application contains one or more modules and one or more assemblies. All these elements are static and are described in metadata produced by the compiler at compile time. The one exception to this rule are elements (such as types, modules, assemblies, etc.) that are created on the fly via Reflection.Emit, which is described in Section 3.10.8.

At runtime, these elements are all contained within an AppDomain. An AppDomain isn’t described with metadata, yet it plays an important role in reflection because it forms the root of the type hierarchy of a .NET application at runtime.

In any given application, the relationship between these units is hierarchical, as depicted by the diagram below:

AppDomain (runtime root of hierarchy)
    Assemblies
      Modules
        Types
          Members
          Nested types

Each of these elements is discussed in the following sections.

Types, members, and nested types

The most basic element that reflection deals with is the type. This class represents the metadata for each type declaration in an application (both predefined and user-defined types).

Types contain members, which include constructors, fields, properties, events, and methods. In addition, types may contain nested types, which exist within the scope of an outer type and are typically used as helper classes. Types are grouped into modules, which are, in turn, contained within assemblies.

Assemblies and modules

Assemblies are the logical equivalent of DLLs in Win32 and the basic unit of deployment, versioning, and reuse for types. In addition, assemblies create a security, visibility, and scope resolution boundary for types (see Section 3.9).

A module is a physical file such as a DLL, an EXE, or a resource (such as GIFs or JPGs). While it isn’t common practice, an assembly can be composed of multiple modules, allowing you to control application working set size, use multiple languages within one assembly, and share a module across multiple assemblies.

AppDomains

From the perspective of reflection, an AppDomain is the root of the type hierarchy and serves as the container for assemblies and types when they are loaded into memory at runtime. A helpful way to think about an AppDomain is to view it as the logical equivalent of a process in a Win32 application.

AppDomains provide isolation, creating a hard boundary for managed code just like the process boundary under Win32. Similar to processes, AppDomains can be started and stopped independently, and application faults take down only the AppDomain the fault occurs in, not the process hosting the AppDomain.

Retrieving the Type for an Instance

At the heart of reflection is System.Type, which is an abstract base class that provides access to the metadata of a type.

You can access the Type class for any instance using GetType, which is a nonvirtual method of System.Object. When you call GetType, the method returns a concrete subtype of System.Type, which can reflect over and manipulate the type.

Retrieving a Type Directly

You can also retrieve a Type class by name (without needing an instance) using the static method GetType on the Type class, as follows:

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

Finally, C# provides the typeof operator, which returns the Type class for any type known at compile time:

Type t = typeof(System.Int32);

The main difference between these two approaches is that Type.GetType is evaluated at runtime and is more dynamic, binding by name; while the typeof operator is evaluated at compile time, uses a type token, and is slightly faster to call.

Reflecting over a Type Hierarchy

Once you have retrieved a Type instance you can navigate the application hierarchy described earlier, accessing the metadata via types that represent members, modules, assemblies, namespaces, AppDomains, and nested types. You can also inspect the metadata and any custom attributes, create new instances of the types, and invoke members.

Here is an example that uses reflection to display the members in three different types:

using System;
using System.Reflection;
class Test {
  static void Main( ) {
    object o = new Object( );
    DumpTypeInfo(o.GetType( ));
    DumpTypeInfo(typeof(int));
    DumpTypeInfo(Type.GetType("System.String"));
  }
  static void DumpTypeInfo(Type t) {
    Console.WriteLine("Type: {0}", t);

    // Retrieve the list of members in the type
    MemberInfo[] miarr = t.GetMembers(BindingFlags.LookupAll);

    // Print out details on each of them
    foreach (MemberInfo mi in miarr)
      Console.WriteLine("  {0}={1}", mi.MemberType.Format( ), mi);
  }
}

Late Binding to Types

Reflection can also perform late binding, in which the application dynamically loads, instantiates, and uses a type at runtime. This provides greater flexibility at the expense of invocation overhead.

In this section, we create an example that uses very late binding, dynamically discovers new types at runtime, and uses them.

In the example one or more assemblies are loaded by name (as specified on the command line) and iterated through the types in the assembly looking for subtypes of the Greeting abstract base class. When one is found, the type is instantiated and its SayHello method invoked, which displays an appropriate greeting.

To perform the runtime discovery of types, we use an abstract base class that’s compiled into an assembly as follows (see the source comment for filename and compilation information):

// Greeting.cs - compile with /t:library
public abstract class Greeting { 
  public abstract void SayHello( );
}

Compiling this code produces a file named Greeting.dll, which the other parts of the sample can use.

We now create a new assembly containing two concrete subtypes of the abstract type Greeting, as follows (see the source comment for filename and compilation information):

// English.cs - compile with /t:library /r:Greeting.dll
using System;
public class AmericanGreeting : Greeting {
  private string msg = "Hey, dude. Wassup!";
  public override void SayHello( ) {
    Console.WriteLine(msg);
  }
}
public class BritishGreeting : Greeting {
  private string msg = "Good morning, old chap!";
  public override void SayHello( ) {
    Console.WriteLine(msg);
  }
}

Compiling the source file English.cs produces a file named English.dll, which the main program can now dynamically reflect over and use.

Now we create the main sample, as follows (see the source comment for filename and compilation information):

// SayHello.cs - compile with /r:Greeting.dll
// Run with SayHello.exe <dllname1> <dllname2> ... <dllnameN>
using System;
using System.Reflection;
class Test {
  static void Main (string[] args) {

    // Iterate over the cmd-line options,
    // trying to load each assembly
    foreach (string s in args) {
      Assembly a = Assembly.LoadFrom(s);
      
      // Pick through all the public type, looking for
      // subtypes of the abstract base class Greeting
      foreach (Type t in a.GetTypes( ))
        if (t.IsSubclassOf(typeof(Greeting))) {

          // Having found an appropriate subtype, create it
          object o = Activator.CreateInstance(t);

          // Retrieve the SayHello MethodInfo & invoke it
          MethodInfo mi = t.GetMethod("SayHello");
          mi.Invoke(o, null);
        }
    }
  }
}

Running the sample now with SayHello English.dll produces the following output:

Hey, dude. Wassup!
Good morning, old chap!

The interesting aspect of the preceding sample is that it’s completely late-bound; i.e., long after the SayHello program is shipped you can create a new type and have SayHello automatically take advantage of it by simply specifying it on the command line. This is one of the key benefits of late binding via reflection.

Activation

In the previous examples, we loaded an assembly by hand and used the System.Activator class to create a new instance based on a type. There are many overrides of the CreateInstance method that provide a wide range of creation options, including the ability to short-circuit the process and create a type directly:

object o = Activator.CreateInstance("Assem1.dll",              
                                    "Friendly.Greeting");

Other capabilities of the Activator type include creating types on remote machines, creating types in specific AppDomains (sandboxes), and creating types by invoking a specific constructor (rather than using the default constructor as these examples show).

Advanced Uses of Reflection

The preceding example demonstrates the use of reflection, but doesn’t perform any tasks you can’t accomplish using normal C# language constructs. However, reflection can also manipulate types in ways not supported directly in C#, as is demonstrated in this section.

While the CLR enforces access controls on type members (specified using access modifiers such as private and protected), these restrictions don’t apply to reflection. Assuming you have the correct set of permissions, you can use reflection to access and manipulate private data and function members, as this example using the Greeting subtypes from the previous section shows (see the source comment for filename and compilation information):

// InControl.cs - compile with /r:Greeting.dll,English.dll
using System;
using System.Reflection;
class TestReflection {
  // Note: This method requires the ReflectionPermission perm.
  static void ModifyPrivateData(object o, string msg) {

    // Get a FieldInfo type for the private data member
    Type t = o.GetType( ); 
    FieldInfo fi = t.GetField("msg", BindingFlags.NonPublic|
                                     BindingFlags.Instance);

    // Use the FieldInfo to adjust the data member value
    fi.SetValue(o, msg);
  }
  static void Main( ) {
    // Create instances of both types
    BritishGreeting bg = new BritishGreeting( );
    AmericanGreeting ag = new AmericanGreeting( );

    // Adjust the private data via reflection
    ModifyPrivateData(ag, "Things are not the way they seem");
    ModifyPrivateData(bg, "The runtime is in total control!");
    
    // Display the modified greeting strings
    ag.SayHello( ); // "Things are not the way they seem"
    bg.SayHello( ); // "The runtime is in total control!"
  }
}

When run, this sample generates the following output:

Things are not the way they seem
The runtime is in total control!

This demonstrates that the private msg data members in both types are modified via reflection, although there are no public members defined on the types that allow that operation. Note that while this technique can bypass access controls, it still doesn’t violate type safety.

Although this is a somewhat contrived example, the capability can be useful when building utilities such as class browsers and test suite automation tools that need to inspect and interact with a type at a deeper level than its public interface.

Creating New Types at Runtime

The System.Reflection.Emit namespace contains classes that can create entirely new types at runtime. These classes can define a dynamic assembly in memory; define a dynamic module in the assembly; define a new type in the module, including all its members; and emit the MSIL opcodes needed to implement the application logic in the members.

Here is an example that creates and uses a new type called HelloWorld with a member called SayHello:

using System;
using System.Reflection;
using System.Reflection.Emit;
public class Test
{
  static void Main( )
  {
    // Create a dynamic assembly in the current AppDomain
    AppDomain ad = AppDomain.CurrentDomain;
    AssemblyName an = new AssemblyName( );
    an.Name = "DynAssembly";
    AssemblyBuilder ab = 
      ad.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
    
    // Create a module in the assembly & a type in the module
    Assembly a = (Assembly)ab;
    ModuleBuilder modb = a.DefineDynamicModule("DynModule");
    TypeBuilder tb = modb.DefineType("AgentSmith", 
                                     TypeAttributes.Public);
 
    // Add a SayHello member to the type 
    MethodBuilder mb = tb.DefineMethod("SayHello",        
                                       MethodAttributes.Public,
                                       null, null);
                                        
    // Generate the MSIL for the SayHello Member
    ILGenerator ilg = mb.GetILGenerator( );
    ilg.EmitWriteLine("Never send a human to do a machine's job.");
    ilg.Emit(OpCodes.Ret);

    // Finalize the type so we can create it
    tb.CreateType( );

    // Create an instance of the new type
    Type t = Type.GetType("AgentSmith");
    object o = Activator.CreateInstance(t);
    
    // Prints "Never send a human to do a machine's job."
    t.GetMethod("SayHello").Invoke(o, null);
  }
}

A common example using Reflection.Emit is the regular-expression support in the BCL, which can emit new types that are tuned to search for specific regular expressions, eliminating the overhead of interpreting the regular expression at runtime.

Other uses of Reflection.Emit in the BCL include dynamically generating transparent proxies for remoting and generating types that perform specific XSLT transforms with the minimum runtime overhead.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.99.71