Understanding Reflection, Part 1

Introduction

Reflection provides a way to examine and manipulate the runtime environment
programmatically. This has several benefits, such as being able to discover types,
methods, and properties at runtime; being able to access and manipulate attributes
at runtime; and being able to invoke new methods at runtime. This article will
focus on the first benefit; future articles will cover the others. The
reflection concept is not unique to .NET -- Java, Eiffel, and SmallTalk all implement
similar concepts to varying degrees. .NET benefits from learning from all of
these earlier systems. Here we will focus on the .NET implementation.

The Great and Mighty Type Object

One common entry point for reflection is the Type
object. Every object has a method, GetType,
that will return the type associated with that object. C# also includes a typeof
operator that will also return the Type object of a given class without having to have a
variable of that type. The Type object itself also includes a GetType
method that will return the Type object associated with a fully qualified type name. As
you can see, it is fairly easy to get access to this object.

Once you have the Type object, you can find out everything you want to know about the
object you are dealing with.

Getting the Properties

The Type object includes a method, GetProperties,
that will return an array of PropertyInfo
objects. To iterate through this property collection, we can use code similar to this:

private void GetProperties (Type theType)
{
// Get the properties in the object
PropertyInfo [] properties = theType.GetProperties ();
// loop through the properties
foreach (PropertyInfo property in properties )
{
// Write out the name and data type for the property
Response.Write (property.Name + " is of type " +
property.PropertyType.ToString() + " and ");
// Determine whether or not the property includes a get method
if (property.CanRead == true)
{
Response.Write ("can be read" );
}
else
{
Response.Write ( "can not be read" );
}
Response.Write (" and ");
// Determine whether or not the property includes a set method
if (property.CanWrite == true)
{
Response.Write ("can be written to");
}
else
{
Response.Write ("cannot be written to");
}
Response.Write ("<br>");
}
}

Or, in VB.NET:

Public Shared Sub GetProperties(ByRef theType As Type)
' sample call GetProperties(GetType(System.Math))
'
' Get the properties in the object
Dim myProperties() As PropertyInfo = _
theType.GetProperties((BindingFlags.Public Or BindingFlags.Instance))
' Loop thru the public properties
Dim PropertyItem As PropertyInfo
For Each PropertyItem In myProperties
With PropertyItem
Response.Write(.Name & " is of type " & .PropertyType.ToString & _
" And ")
' determine whether or not the property inclues a get
If .CanRead Then
Response.Write("can be read and ")
Else
Response.Write("cannot be read and ")
End If
' determine whehter or not the property includes a set
If .CanWrite Then
Response.WriteLine("can be written.")
Else
Response.Write("cannot be written.")
End If
End With
Response.Write ("<br>")
Next
End Sub

Running this example will result in a page that looks like the one in Figure 1:

Figure 1. Property Results

It is very easy to programmatically interrogate an object about its
properties.

Getting the Methods

We can also easily get details about the methods included in a class using the GetMethods
method, which will return an array of MethodInfo
objects. To iterate through this method collection, we can use code similar to this:

private void GetMethods (Type theType)
{
// Get the methods in the Object
MethodInfo [] MethodInfoArray =
theType.GetMethods();
// Loop through each of the methods
foreach (MethodInfo method in MethodInfoArray )
{
// Write out the name and
// return type for this method
Response.Write (method.Name + " returns " + method.ReturnType.ToString());
// Determine whether or not a method is public or
// private. Note that private methods are
// included in the array
if (method.IsPrivate == true)
{
Response.Write (" but is private and cannot be executed ");
}
// Determine whether or not the method is static
// (shared in VB) or whether an object reference
// wil be required
if (method.IsStatic == true)
{
Response.Write (" and does not require an object
reference to be executed ");
}
// See if this method is a constructor
if (method.IsConstructor == true)
{
Response.Write (" and is the constructor for " + theType.Name);
}
Response.Write ("<br>");
}
}

Or, in VB.NET:

Public Shared Sub GetMethods(ByRef theType As Type)
' sample call GetMethods(GetType(System.Math))
'
' Get the methods in the object
Dim myMethods() As MethodInfo = theType.GetMethods
' Loop thru the methods
Dim MethodItem As MethodInfo
For Each MethodItem In myMethods
With MethodItem
Response.Write(.Name & "returns " & .ReturnType.ToString)
' determine if method public or private -- note private methods
' ARE in array
If .IsPrivate Then
Response.Write(" is private and can NOT be executed ")
End If
' determine if shared (static)
If .IsStatic Then
Response.Write("and does NOT require instantiation ")
End If
If .IsConstructor Then
Response.Write(" and is the contructor for " & theType.Name)
End If
Response.Write ("<br>")
End With
Next
End Sub

Running this example will result in a page that looks like the one in Figure
2:

Figure 2. Method Results

Getting the Parameters

All of this information about the methods is great, but to be really useful,
you also need to know the parameters for these methods. Fortunately, each
MethodInfo object also includes a GetParameters method that will return an array of ParameterInfo
objects. We can use these parameter info objects to find out what
parameters a given method expects. To iterate through these parameter collections, we can use code
similar to this:

Public Shared Sub GetParameters(ByRef theType As Type)
' sample call GetParameters(GetType(System.Math))
'
' Get the list of methods
Dim myMethods() As MethodInfo = theType.GetMethods
' Loop thru the methods
Dim MethodItem As MethodInfo
For Each MethodItem In myMethods
Response.WriteLine()
Response.WriteLine(MethodItem.Name & _
" takes the following parameters: ")
' Get the parameters for each method
Dim myParms() As ParameterInfo = MethodItem.GetParameters
' point out if there are no parameters
If myParms.Length = 0 Then
Response.WriteLine("<li>No parameters.")
End If
Dim ParmItem As ParameterInfo
' output some details for each parameter
For Each ParmItem In myParms
With ParmItem
Response.Write("<li>" & .Name & " of type " & _
.ParameterType.ToString)
End With
Response.Write ("</ul>")
Next
Next
End Sub

Running this example will result in a page that looks like the one in Figure
3:

Figure 3. Parameter Results

Finding All of the Types in the Same Namespace

One level higher in the reflection hierarchy than the Type is the Assembly, where the Type resides. The Type object provides ready access to the
containing Assembly through the Assembly property. The Assembly object provides access to
each of the Types contained in that Assembly through the GetTypes method, which will return an array of Types. To filter this list of Types
to a single NameSpace, we will include a conditional statement to only show the ones
where the NameSpace matches the namespace that we started with.

Public Shared Sub GetTypes(ByRef theType As Type)
' sample call GetTypes(GetType(System.Data.Column))
'
'Get the assembly for the Type passed in
Dim TargetAssembly As [Assembly] = theType.Assembly
' Find out which namespace this Type is in
Dim TargetNamespace As String = theType.Namespace
Response.Write ("<h4>Types in the " TargetNameSpace _
" namespace</h4>")
' Get an array of the Types that are in this assembly
Dim TargetTypes() As Type = TargetAssembly.GetTypes
Dim TypeItem As Type
' Loop through these types
For Each TypeItem In TargetTypes
With TypeItem
' Display information only for the Types that
' are in the target namespace
If .Namespace = TargetNamespace Then
Response.Write(.Name & " is a ")
If .IsPublic Then
Response.Write("public ")
Else
Response.Write("private ")
End If
If .IsClass Then
Response.WriteLine("Class.")
If .IsInterface Then
Response.WriteLine("Interface.")
If .IsEnum Then
Response.WriteLine("Enumeration.")
End If
Response.Write ("<BR>");
End If
End With
Next
End Sub

Conclusion

Here we have seen how easy it is to query the runtime environment and get the
details about the objects we are using. These techniques can be very useful,
both in documenting the objects that we are using and in learning to use
new classes. Next time, we will investigate how to use reflection with custom-defined attributes to extend the metadata available at runtime.

Nick Harrison
UNIX-programmer-turned-.NET-advocate currently working in Charlotte, North Carolina using .NET to solve interesting problems in the mortgage industry.