Understanding Reflection, Part 1
by Nick Harrison10/06/2003
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.
|
Related Reading .NET Framework Essentials |
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
Calling this function in a web page with a line similar to this:
private void Page_Load(object sender, System.EventArgs e)
{
GetProperties (typeof (System.Web.UI.Page));
}
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
Calling this function from a web page with a line similar to this:
private void Page_Load(object sender, System.EventArgs e)
{
GetMethods (typeof (System.Math));
}
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:
private void GetParameters (Type theType)
{
// Get an Array of methods
MethodInfo [] MethodInfoArray = theType.GetMethods ();
// Loop through the methods
foreach (MethodInfo method in MethodInfoArray )
{
Response.Write (method.Name +
" takes the following parameters: <ul>");
// Get the parameters for each method
ParameterInfo [] parameters = method.GetParameters ();
// Point out if there are no parameters
if (parameters.GetLength (0) == 0)
{
Response.Write ("<li>No Parameters");
}
// Output some details for each parameter
foreach (ParameterInfo param in parameters )
{
Response.Write ("<li>"+param.Name + " of type " +
param.ParameterType.ToString());
}
Response.Write ("</ul>");
}
}
Or, in VB.NET:
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
Calling this function from a web page with a line similar to this:
private void Page_Load(object sender, System.EventArgs e)
{
GetParameters (typeof (System.Math));
}
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.
private void GetTypes (Type theType )
{
// Get the assembly for the type passed in
Assembly theAssembly = theType.Assembly;
// Find out which namespace this type is in
string TargetNameSpace = theType.Namespace;
Response.Write ("<h4>Types in the " + NameSpace +
" namespace</h4>");
// Get an array of the types in this assembly
Type [] types = theAssembly.GetTypes ();
// Loop through these types
foreach (Type currentType in types)
{
// Display information only for the Types that are in the target assembly
if (currentType.Namespace == TargetNameSpace )
{
Response.Write ( currentType.Name + " is a ");
if (currentType.IsPublic)
{
Response.Write (" public " );
}
else
{
Response.Write (" private ");
}
if (currentType.IsClass == true)
{
Response.Write ( "Class");
}
if (currentType.IsInterface == true)
{
Response.Write ( "Interface");
}
if (currentType.IsEnum == true)
{
Response.Write ( "Enumeration");
}
Response.Write ("<BR>");
}
}
}
Or, in VB.NET:
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
Calling this function from a web page with a line similar to this:
private void Page_Load(object sender, System.EventArgs e)
{
GetTypes (typeof (System.Data.DataColumn));
}
Running this example will result in a page similar to Figure 4:
|
| Figure 4. Types Results |
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.
Return to ONDotnet.com


