8.2. Runtime Type Reflection

The System.Reflection namespace defines a set of special Info classes, such as the MethodInfo class we saw in the previous section, that provide access to the attributes and metadata of constructors, events, fields, methods, parameters, properties, and so on. These classes are listed in Table 8.1.

Table 8.1. Info Classes within the System.Reflection Namespace
class Description
MemberInfo Discovers the attributes of a member and provides access to member metadata. Abstract base class.
MethodInfo Discovers the attributes of a method and provides access to method metadata.
ParameterInfo Discovers the attributes of a parameter and provides access to parameter metadata.
ConstructorInfo Discovers the attributes of a class constructor and provides access to constructor metadata.
PropertyInfo Discovers the attributes of a property and provides access to property metadata.
FieldInfo Discovers the attributes of a field and provides access to field metadata.
EventInfo Discovers the attributes of an event and provides access to event metadata.

All reflection operations are rooted in the abstract Type class. Access of the reflection Info classes goes through the associated Type object of a type. The Type class is the key to accessing type information of objects at runtime.

There are three primary methods of retrieving a Type object.If we have an arbitrary object, its associated Type object is retrieved by an invocation of the inherited nonvirtual GetType() method of the Object class:

public static
void TypeDisplay( object o )
{
    Type t = o.GetType();

    // ... OK: now we have metadata access
}

// example of TypeDisplay() invocation

TextQuery tq = new TextQuery();
TypeDisplay( tq );

We can explicitly request the associated Type object by passing the static GetType() method of Type a string containing the name of the type:

public static void TypeDisplay( string t_name )
{
      Type t = Type.GetType( t_name );

      if ( t == null )
           // couldn't find it ...
           return;

      // ... OK: now we have metadata access
}

// an unqualified name ... note that it won't be found
// if the type is defined within a namespace
t.TypeDisplay( "Math" );

// a fully qualified name
t.TypeDisplay( "System.Math" );

// a fully qualified name and assembly
t.TypeDisplay( "System.Math, mscorlib" );

We can get all Type instances or a specific instance of a Type from an Assembly object. We saw an example of how to do this in the previous section.

The next step, once we have the Type object, is to retrieve the associated Info class objects. For example, imagine that we were curious about the type and the get or set accessibility of the Length property of the string class. How might we obtain that information?

First we grab the Type object of a string instance:

string s = "a simple string";
Type t = s.GetType();

Next we invoke the GetProperty() method of the Type class, specifying the Length property by name:

PropertyInfo pi = t.GetProperty( "Length" );

Now that we have the PropertyInfo object for the Length property, we can probe it as deeply as we wish. For example, the following output:

Length is of type Int32
Can Read? True
Can Write? False
Actual value is 15

is generated by the following code sequence:

Console.WriteLine( "{0} is of type {1}",
                    pi.Name, pi.PropertyType );

Console.WriteLine( "Can Read? {0}
Can Write? {1}",
                    pi.CanRead, pi.CanWrite );

Console.WriteLine( "Actual value is {0}",
                    pi.GetValue( s, null ));

What if we had no idea which and how many properties the string class contained? How would we obtain that information? The Type class provides a pair of Get methods. One instance of the pair returns the Info object associated with a specified member; this is what we did with GetProperty(). The second instance by default retrieves all public members of the particular type as an array of Info objects. (If the invocation fails to retrieve any members, an empty array, not null, is returned.) To obtain the information for all properties, we use a method called GetProperties():

PropertyInfo [] parray = t.GetProperties();
Console.WriteLine( "{0} has {1} properties",
                   t.FullName, parray.Length );

We can then iterate through the array, examining each element in turn:

foreach ( PropertyInfo pinfo in parray )
{
  // same Console.WriteLine as above ...
  Console.WriteLine("Actual value is {0}",
                       pi.GetValue(s,null));
}

Unfortunately, this last invocation of GetValue() results in an exception. The reason is that the GetProperties() method retrieves both properties and indexers. The second argument passed to GetValue() needs to be null for a property. For an indexer, however, the second argument must be an array holding the same number of values as the number of dimensions supported by the indexer. The value placed in the array represents the position from which to get the value. So for our code to work in general, we must do the following:

First we need to determine whether the PropertyInfo object addresses a property or an indexer. We do that by a call to GetIndexParameters():

ParameterInfo [] pif = pinfo.GetIndexParameters();

If pif is null, the member addressed by pinfo is a property. Things are simple: We pass in a null second argument. Otherwise, GetValue() expects an array of type object for its second argument, with each element holding an appropriate value of each index type. How do we determine both the number and the type of the indexer parameters?

The length of the returned array represents the number of indices supported by the indexer. ParameterInfo provides the ParameterType property that returns a Type object for the parameter. The Position property indicates the position of the parameter. The position is zero-based. The first parameter, that is, is associated with a position value of 0.

ParameterInfo [] pif = pinfo.GetIndexParameters();

if ( pif.Length != 0 )
{
   Console.WriteLine( "{0} is an indexer of {1} indices",
                        pinfo.Name, pif.Length );

   foreach ( ParameterInfo parm in pif )
                Console.WriteLine( "index {0} is of type {1}",
                      parm.Position+1,
                      parm.ParameterType );
}

It turns out that the String class has two public properties: a char indexer of one dimension named Chars, and the Length property of type int. Here is the result of our query:

System.String has 2 properties

Chars is an indexer of 1 indices
index 1 is of type Int32
Chars is of type Char
Can Read? True
Can Write? False
First index value is A

Length is of type Int32
Can Read? True Can Write? False
The Property value is 15

Hopefully this discussion has illustrated at least in part how conceptually different runtime programming is from the compile-time programming that we have been doing throughout the rest of the text. Runtime programming requires greater attention to the implementation details of the type system. It provides a great deal of flexibility, but because of its interaction with the runtime environment, it is significantly slower in performance.

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

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