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.
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.
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 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.
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.
AppDomain
s provide isolation, creating a hard
boundary for managed code just like the process boundary under Win32.
Similar to processes, AppDomain
s can be started
and stopped independently, and application faults take down only the
AppDomain
the fault occurs in, not the process
hosting the
AppDomain
.
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.
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.
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, AppDomain
s, 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); } }
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.
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
AppDomain
s (sandboxes), and creating types by
invoking a specific constructor (rather than using the default
constructor as these examples show).
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.
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.
3.146.37.250