For the
attributes in the metadata to be useful, you need a way to access
them—ideally during runtime. The classes in the
Reflection
namespace, along with the
System.Type
and
System.TypedReference
classes, provide support for
examining and interacting with the metadata.
Reflection is generally used for any of four tasks:
This might be used by tools and utilities that wish to display metadata.
This allows you to examine the types in an assembly and interact with or instantiate those types. This can be useful in creating custom scripts. For example, you might want to allow your users to interact with your program using a script language, such as JavaScript, or a scripting language you create yourself.
This allows the programmer to invoke properties and methods on objects dynamically instantiated based on type discovery. This is also known as dynamic invocation.
The ultimate use of reflection is to create new types at runtime and then to use those types to perform tasks. You might do this when a custom class, created at runtime, will run significantly faster than more generic code created at compile time. An example is offered later in this chapter.
In this section, you will use the C# Reflection support to read the
metadata in the MyMath
class.
You start by initializing an object of the type
MemberInfo
. This object, in the
System.Reflection
namespace, is provided to discover
the attributes of a member and to provide access to the metadata:
System.Reflection.MemberInfo inf = typeof(MyMath);
You call the typeof
operator on the
MyMath
type, which returns an object of type
Type
, which derives from
MemberInfo.
The Type
class is the root of the reflection
classes.
Type
encapsulates a representation of the type of
an object. The Type
class is the primary way to
access metadata. MemberInfo
derives from
Type
and encapsulates information about the
members of a class (e.g., methods, properties, fields, events, etc.).
The next step is to call GetCustomAttributes
on
this MemberInfo
object, passing in the type of the
attribute you want to find. What you get back is an array of objects,
each of type BugFixAttribute
:
object[] attributes; attributes = inf.GetCustomAttributes(typeof(BugFixAttribute),false);
You can now iterate through this array, printing out the properties
of the BugFixAttribute
object. Example 18-2 replaces the Tester
class
from Example 18-1.
Example 18-2. Using Reflection
public static void Main( )
{
MyMath mm = new MyMath( );
Console.WriteLine("Calling DoFunc(7). Result: {0}",
mm.DoFunc1(7));
// get the member information and use it to
// retrieve the custom attributes
System.Reflection.MemberInfo inf = typeof(MyMath);
object[] attributes;
attributes =
inf.GetCustomAttributes(
typeof(BugFixAttribute), false);
// iterate through the attributes, retrieving the
// properties
foreach(Object attribute in attributes)
{
BugFixAttribute bfa = (BugFixAttribute) attribute;
Console.WriteLine("
BugID: {0}", bfa.BugID);
Console.WriteLine("Programmer: {0}", bfa.Programmer);
Console.WriteLine("Date: {0}", bfa.Date);
Console.WriteLine("Comment: {0}", bfa.Comment);
}
}
Output:
Calling DoFunc(7). Result: 9.3333333333333339
BugID: 121
Programmer: Jesse Liberty
Date: 01/03/05
Comment:
BugID: 107
Programmer: Jesse Liberty
Date: 01/04/05
Comment: Fixed off by one errors
When you put this replacement code into Example 18-1 and run it, you can see the metadata printed as you’d expect.
You can use reflection to explore and examine the contents of an assembly. You can find the types associated with a module; the methods, fields, properties, and events associated with a type, as well as the signatures of each of the type’s methods; the interfaces supported by the type; and the type’s base class.
To start, load an assembly dynamically with the
Assembly.Load
static method. The
Assembly
class encapsulates the actual assembly
itself, for purposes of reflection. The signature for the
Load
method is:
public static Assembly.Load(AssemblyName)
For the next example, pass in the Core Library to the
Load
method. MsCorLib.dll
has
the core classes of the .NET Framework:
Assembly a = Assembly.Load("Mscorlib.dll");
Once the assembly is loaded, you can call GetTypes( )
to return an array of Type
objects.
The Type
object is the heart of reflection.
Type
represents type declarations: classes,
interfaces, arrays, values, and enumerations:
Type[] types = a.GetTypes( );
The assembly returns an array of types that you can display in
a
foreach
loop, as shown in Example 18-3. Because this listing uses the
Type
class, you will want to add a
using
statement for the
System.Reflection
namespace.
Example 18-3. Reflecting on an assembly
namespace Programming_CSharp { using System; using System.Reflection; public class Tester { public static void Main( ) { // what is in the assembly Assembly a = Assembly.Load("Mscorlib.dll"); Type[] types = a.GetTypes( ); foreach(Type t in types) { Console.WriteLine("Type is {0}", t); } Console.WriteLine( "{0} types found", types.Length); } } }
The output from this would fill many pages. Here is a short excerpt:
Type is System.TypeCode Type is System.Security.Util.StringExpressionSet Type is System.Runtime.InteropServices.COMException Type is System.Runtime.InteropServices.SEHException Type is System.Reflection.TargetParameterCountException Type is System.Text.UTF7Encoding Type is System.Text.UTF7Encoding$Decoder Type is System.Text.UTF7Encoding$Encoder Type is System.ArgIterator Type is System.Runtime.Remoting.JITLookupTable Type is System.Runtime.Remoting.IComponentServices Type is System.Runtime.Remoting.ComponentServices 1429 types found
This example obtained an array filled with the types from the Core Library and printed them one by one. The array contained 1,429 entries on my machine.
You can reflect on a single type in the
mscorlib
assembly as well. To do so, you extract a
type from the assembly with the GetType( )
method, as shown in Example 18-4.
Example 18-4. Reflecting on a type
namespace Programming_CSharp
{
using System;
using System.Reflection;
public class Tester
{
public static void Main( )
{
// examine a single object
Type theType =
Type.GetType(
"System.Reflection.Assembly");
Console.WriteLine(
"
Single Type is {0}
", theType);
}
}
}
Output:
Single Type is System.Reflection.Assembly
You can ask the Assembly
type for all its members
using the GetMembers( )
method of the Type
class, which lists all the methods, properties, and fields, as shown
in Example 18-5.
Example 18-5. Reflecting on the members of a type
namespace Programming_CSharp { using System; using System.Reflection; public class Tester { public static void Main( ) { // examine a single object Type theType = Type.GetType( "System.Reflection.Assembly"); Console.WriteLine( " Single Type is {0} ", theType); // get all the members MemberInfo[] mbrInfoArray = theType.GetMembers( ); foreach (MemberInfo mbrInfo in mbrInfoArray ) { Console.WriteLine("{0} is a {1}", mbrInfo, mbrInfo.MemberType); } } } }
Once again the output is quite lengthy, but within the output you see fields, methods, constructors, and properties, as shown in this excerpt:
System.String s_localFilePrefix is a Field Boolean IsDefined(System.Type) is a Method Void .ctor( ) is a Constructor System.String CodeBase is a Property System.String CopiedCodeBase is a Property
You might want to focus on methods only, excluding the fields,
properties, and so forth. To do so, you remove the call to
GetMembers( )
:
MemberInfo[] mbrInfoArray = theType.GetMembers(BindingFlags.LookupAll);
and add a call to GetMethods( )
:
mbrInfoArray = theType.GetMethods( );
The output now is nothing but the methods:
Output (excerpt):
Boolean Equals(System.Object) is a Method
System.String ToString( ) is a Method
System.String CreateQualifiedName(
System.String, System.String) is a Method
System.Reflection.MethodInfo get_EntryPoint( ) is a Method
Finally, to narrow it down even further, you can use the
FindMembers
method to find particular members of
the type. For example, you can narrow your search to methods whose
names begin with the letters Get
.
To narrow the search, you use the
FindMembers
method, which takes four parameters:
MemberTypes
, BindingFlags
,
MemberFilter
, and object
.
BindingFlags
An enumeration that controls the way searches are conducted by
reflection. There are a great many BindingFlag
values, including IgnoreCase
,
Instance
, Public
,
Static
, and so forth. The
BindingFlags
default member indicates no binding
flag, which is what you want because you do not want to restrict the
binding.
MemberFilter
A delegate (see Chapter 12) that is used to filter
the list of members in the MemberInfo array of objects. The filter
you’ll use is Type.FilterName
, a field of
the Type
class used for filtering on a name.
The complete listing for filtering on these methods is shown in Example 18-6.
Example 18-6. Finding particular members
namespace Programming_CSharp
{
using System;
using System.Reflection;
public class Tester
{
public static void Main( )
{
// examine a single object
Type theType = Type.GetType(
"System.Reflection.Assembly");
// just members which are methods beginning with Get
MemberInfo[] mbrInfoArray =
theType.FindMembers(MemberTypes.Method,
BindingFlags.Default,
Type.FilterName, "Get*");
foreach (MemberInfo mbrInfo in mbrInfoArray )
{
Console.WriteLine("{0} is a {1}",
mbrInfo, mbrInfo.MemberType);
}
}
}
Output (excerpt):
System.Type[] GetTypes( ) is a Method
System.Type[] GetExportedTypes( ) is a Method
System.Type GetType(System.String, Boolean) is a Method
System.Type GetType(System.String) is a Method
System.Reflection.AssemblyName GetName(Boolean) is a Method
System.Reflection.AssemblyName GetName( ) is a Method
Int32 GetHashCode( ) is a Method
Once you have
discovered a method, it’s possible to invoke it using
reflection. For example, you might like to invoke the Cos( )
method of System.Math
, which returns the cosine of
an angle.
You could, of course, call Cos( )
in the normal
course of your code, but reflection allows you to bind to that method
at runtime. This is called late-binding and
offers the flexibility of choosing at runtime which object you will
bind to and invoking it programmatically. This can be useful when
creating a custom script to be run by the user or when working with
objects that might not be available at compile time. For example, by
using late-binding, your program can interact with the spellchecker
or other components of a running commercial word processing program
such as Microsoft Word.
To invoke Cos( )
, you will first get the
Type
information for the
System.Math
class:
Type theMathType = Type.GetType("System.Math");
With that type information, you can dynamically load an instance of
that class by using a static method of the
Activator
class.
The Activator
class contains four methods, all
static, which you can use to create objects locally or remotely or to
obtain references to existing objects. The four methods are:
CreateComInstanceFrom
,
CreateInstanceFrom
, GetObject
,
and CreateInstance
:
Used to create instances of COM objects.
Used to create a reference to an object from a particular assembly and type name.
Used when marshaling objects. Marshaling is discussed in detail in Chapter 19.
You now have two objects in hand: a Type
object
named TheMathType
, which you created by calling
GetType
, and an instance of the
System.Math
class named theObj
,
which you instantiated by calling CreateInstance
.
Before you can invoke a method on the object, you must get the method
you need from the Type
object,
theMathType
. To do so, you’ll call
GetMethod()
, and you’ll pass in the
signature of the Cos
method.
The signature, you will remember, is the name of the method
(Cos
) and its parameter types. In the case of
Cos()
, there is only one parameter: a double.
Whereas, Type.GetMethod
takes two parameters: the
first represents the name of the method you want, and the second
represents the parameters. The name is passed as a string; the
parameters are passed as an array of types:
MethodInfo CosineInfo = theMathType.GetMethod("Cos",paramTypes);
Before calling GetMethod
, you must prepare the
array of types:
Type[] paramTypes = new Type[1]; paramTypes[0]= Type.GetType("System.Double");
This code declares the array of Type
objects and
then fills the first element (paramTypes[0]
) with
a Type
representing a double. You obtain that type
representing a double by calling the static method
Type.GetType()
, passing in the string
"System.Double"
.
You now have an object of type MethodInfo
on which
you can invoke the method. To do so, you must pass in the actual
value of the parameters, again in an array:
Object[] parameters = new Object[1]; parameters[0] = 45; Object returnVal = CosineInfo.Invoke(theObj,parameters);
Note that you’ve created two arrays. The first,
paramTypes
, holds the type of the parameters. The
second, parameters
, holds the actual value. If the
method had taken two arguments, you’d have declared these
arrays to hold two values. If the method took no values, you still
would create the array, but you would give it a size of zero!
Type[] paramTypes = new Type[0];
Odd as this looks, it is correct.
Example 18-7 illustrates dynamically calling the
Cos( )
method.
Example 18-7. Dynamically invoking a method
namespace Programming_CSharp { using System; using System.Reflection; public class Tester { public static void Main( ) { Type theMathType = Type.GetType("System.Math"); Object theObj = Activator.CreateInstance(theMathType); // array with one member Type[] paramTypes = new Type[1]; paramTypes[0]= Type.GetType("System.Double"); // Get method info for Cos( ) MethodInfo CosineInfo = theMathType.GetMethod("Cos",paramTypes); // fill an array with the actual parameters Object[] parameters = new Object[1]; parameters[0] = 45; Object returnVal = CosineInfo.Invoke(theObj,parameters); Console.WriteLine( "The cosine of a 45 degree angle {0}", returnVal); } } }
That was a lot of work just to invoke a single method. The power, however, is that you can use reflection to discover an assembly on the user’s machine, use reflection to query what methods are available, and then use reflection to invoke one of those members dynamically!
18.118.226.240