Team LiB
Previous Section Next Section

Object Identities

When we discussed two of the C# collection classes—ArrayList and Hashtable— earlier, we spoke about the need to cast an object reference back into the appropriate reference type when extracting an object from one of these collections. This may lead one to believe that an object somehow "forgets" what class it belongs to, but this isn't true. An object always retains its class identity; it's simply that we can refer to an object with various different reference variables, which may be declared to be of different types, and it's this phenomenon that affects what messages the compiler believes that an object is capable of responding to. To help illustrate this point, let's use an example.

A Professor Is a Person and an Object, Rolled Into One

If we instantiate a Professor object, which is a type of Person, and therefore, as we saw earlier, is also a type of Object, we can think of the Professor object as being allocated in memory as demonstrated in Figure 13-14.

Click To expand
Figure 13-14: A Professor object is simultaneously a Person object and an Object object, all rolled into one!

We can then create reference variables of varying types (namely, any of the types in this object's derivation chain) to store handles on this Object/Person/ Professor; each reference refers to a different "rendition" of the object:

// Create a Professor object, and maintain three handles on it of varying types.
Professor pr = new Professor();
Person p = pr;
object o = pr;

If we refer to this Professor object by its object reference o, then, as far as the compiler is concerned, the only aspects of the object that exist are its Object "core"; the "Person-ness" and "Professor-ness" of the object is in question (see Figure 13-15). So, the compiler will reject any attempts to access the Professor- or Person-defined features of o:

Click To expand
Figure 13-15: The compiler only knows about this Professor object's "Object-ness."
// The compiler would reject this attempt to invoke the AddAdvisee() method,
// declared for the Professor class, on this object, because even though WE
// know from the code above that it is really a Professor whose handle is
// stored as an object, the compiler cannot be certain of this.
o.AddAdvisee();  // compiler error

// However, we can invoke any of the methods that this Professor inherited
// from the Object class:
o.ToString();

If we instead refer to the Professor object by its Person reference p, then, as far as the compiler is concerned, the only aspects of the object that exist are its Object "core" and its Person "extensions"; the "Professor-ness" of the object is in question (see Figure 13-16):

Click To expand
Figure 13-16: The compiler now recognizes this Professor object's "Object-ness" and "Person-ness."
// The compiler would again reject this attempt to invoke the AddAdvisee()
// method, declared for the Professor class, on this object, because even
// though WE know from the code above that it is really a Professor whose
// handle is stored as a Person at run time, the compiler cannot be certain
// of this at COMPILE time.
p.AddAdvisee(); // compiler error

// However, we can invoke any of the methods that this Professor inherited
// from EITHER the Object class ...
p.ToString();

// ... OR the Person class:
p.Name;

Only if we refer to the object by its Professor handle, pr, will the compiler allow us to access Professor-specific features of the object.

Casting, Revisited

Now, back to casting: when a reference to an object like a Professor is stored in an ArrayList, it gets stored in the ArrayList as a generic object reference. When this reference is subsequently retrieved from the ArrayList, the compiler has no way to be certain that the object referred to by the reference is anything but a generic object. Because we, as programmers, know that the object is in reality a Professor, we can inform the compiler that it's so by performing a cast:

Professor p1 = new Professor();
Professor p2 = new Professor();

ArrayList list = new ArrayList();
list.Add(p1);
list.Add(p2);

for (int i = 0; i < list.Count; i++) {
  // Cast the object reference that was stored in the
  // ArrayList back into a Professor reference.
  Professor pr = (Professor) list[i];
  // We may now manipulate the reference as a true Professor.
  pr.ListAdvisees();

  // etc.
}

What would happen if we tried to cast the retrieved object to an inappropriate type? For example, if we placed a reference to a Professor object in the ArrayList, but then tried to cast it to be a Student reference when we retrieved it from the ArrayList? The compiler would trust that we knew what we were doing, and wouldn't produce an error; however, at run time, an exception would be thrown, for there is no way to "morph" a Professor object into a Student object via a simple cast.

Fortunately, there are ways for us to programmatically determine the type of an object reference at run time, as we'll now discuss.

Determining the Class That an Object Belongs To

There are several ways to ask an object what class it belongs to at run time; we can take advantage of

  • The GetType method defined by the Object class

  • The typeof operator

The GetType Method

As mentioned earlier, by virtue of being derived from the Object class, every object inherits a method with the header:

public Type GetType()

When invoked on an object, this method returns an object of type Type, representing the type of the object on which the GetType method has been invoked. For example, if the Person class is defined to belong to the SRS namespace, then the following invocation of GetType:

Person p = new Person();
Type t = p.GetType();

would yield a Type object representing the class SRS.Person. The Type class, in turn, defines a string property named FullName that can be used to access the fully qualified name of the type:

Person p = new Person();
Type t = p.GetType();
String s = t.FullName; // s now equals "SRS.Person".

Chaining the method call and property invocation together, we can ask any object reference to identify which class the object it refers to belongs to, as follows:

reference.GetType().FullName;

For example:

using System;
using System.Collections;

public class CastingExample
{
  static void Main() {
    Student s = new Student();
    Professor p = new Professor();

    ArrayList list = new ArrayList();
    list.Add(s);
    list.Add(p);

    for (int i = 0; i < list.Count; i++) {
      // Note that we are not casting the objects here!
      // We're pulling them out as generic objects.
      object o = list[i];
      Console.WriteLine(o.GetType().FullName);
    }
  }
}

This program produces as output (assuming that neither Student nor Professor belong to a named namespace):

Student
Professor

This demonstrates that the objects themselves really do remember their "roots"!

The typeof Operator

Another way to test whether a given object reference belongs to a particular class is via the typeof operator. This operator takes as its argument the name of a type (note: the type name is not quoted) and returns the corresponding Type object. Here is a simple code snippet to illustrate how the typeof operator can be used to see if a reference is of a certain type. (The == operator works in this case because there is only one System.Type object for each type in the C# language, whether user-defined or predefined.)

    Student s = new Student();

    // Determine if the type of the s reference variable is
    // is equal to the Student type.
    if (s.GetType() == typeof(Student)) {
      Console.WriteLine("s is a Student");
    }

The preceding code would produce the following as output:

s is a Student

Note that if we wish to refer to the name of a class that belongs to an explicitly named namespace, and we haven't included the appropriate using statement with our code, we must fully qualify the class name when using typeof:

// Assume that Bar is a class in the Foo namespace.
if (x.GetType() == typeOf(Foo.Bar) { ... }
Note?/td>

While the use of parentheses to surround the name of a class— typeof(Student)—makes it look as if typeof is a method, it's indeed an operator.


Team LiB
Previous Section Next Section