The object Type

object (System.Object) is the ultimate base class for all types. Any type can be implicitly upcast to object.

To illustrate how this is useful, consider a general-purpose stack. A stack is a data structure based on the principle of “last in, first out” (LIFO). A stack has two operations: push an object on the stack, and pop an object off the stack. Here is a simple implementation that can hold up to 10 objects:

public class Stack
{
  int position;
  object[] data = new object[10];
  public void Push (object o) { data[position++] = o; }
  public object Pop() { return data[--position]; }
}

Because Stack works with the object type, we can Push and Pop instances of any type to and from the Stack:

Stack stack = new Stack();
stack.Push ("sausage");
string s = (string) stack.Pop();   // Downcast
Console.WriteLine (s);             // sausage

object is a reference type, by virtue of being a class. Despite this, value types, such as int, can also be cast to and from object. To make this possible, the CLR must perform some special work to bridge the underlying differences between value and reference types. This process is called boxing and unboxing.

Note

In the section Generics, we’ll describe how to improve our Stack class to better handle stacks with same-typed elements.

Boxing and Unboxing

Boxing is the act of casting a value-type instance to a reference-type instance. The reference type may be either the object class or an interface (see Interfaces). In this example, we box an int into an object:

int x = 9;
object obj = x;           // Box the int

Unboxing reverses the operation, by casting the object back to the original value type:

int y = (int)obj;         // Unbox the int

Unboxing requires an explicit cast. The runtime checks that the stated value type matches the actual object type, and throws an InvalidCastException if the check fails. For instance, the following throws an exception, because long does not exactly match int:

object obj = 9;       // 9 is inferred to be of type int
long x = (long) obj;  // InvalidCastException

The following succeeds, however:

object obj = 9;
long x = (int) obj;

As does this:

object obj = 3.5;      // 3.5 inferred to be type double
int x = (int) (double) obj;    // x is now 3

In the last example, (double) performs an unboxing and then (int) performs a numeric conversion.

Boxing copies the value-type instance into the new object, and unboxing copies the contents of the object back into a value-type instance:

int i = 3;
object boxed = i;
i = 5;
Console.WriteLine (boxed);    // 3

Static and Runtime Type Checking

C# checks types both statically (at compile time) and at runtime.

Static type checking enables the compiler to verify the correctness of your program without running it. The following code will fail because the compiler enforces static typing:

int x = "5";

Runtime type checking is performed by the CLR when you downcast via a reference conversion or unboxing:

object y = "5";
int z = (int) y;       // Runtime error, downcast failed

Runtime type checking is possible because each object on the heap internally stores a little type token. This token can be retrieved by calling the GetType method of object.

The GetType Method and typeof Operator

All types in C# are represented at runtime with an instance of System.Type. There are two basic ways to get a System.Type object: call GetType on the instance, or use the typeof operator on a type name. GetType is evaluated at runtime; typeof is evaluated statically at compile time.

System.Type has properties for such things as the type’s name, assembly, base type, and so on. For example:

int x = 3;

Console.Write (x.GetType().Name);               // Int32
Console.Write (typeof(int).Name);               // Int32
Console.Write (x.GetType().FullName);    // System.Int32
Console.Write (x.GetType() == typeof(int));     // True

System.Type also has methods that act as a gateway to the runtime’s reflection model. For detailed information, see Chapter 19 of C# 5.0 in a Nutshell.

Object Member Listing

Here are all the members of object:

public extern Type GetType();
public virtual bool Equals (object obj);
public static bool Equals (object objA, object objB);
public static bool ReferenceEquals (object objA,
                                    object objB);
public virtual int GetHashCode();
public virtual string ToString();
protected override void Finalize();
protected extern object MemberwiseClone();

Equals, ReferenceEquals, and GetHashCode

The Equals method in the object class is similar to the == operator, except that Equals is virtual, whereas == is static. The following example illustrates the difference:

object x = 3;
object y = 3;
Console.WriteLine (x == y);        // False
Console.WriteLine (x.Equals (y));  // True

Because x and y have been cast to the object type, the compiler statically binds to object’s == operator, which uses reference-type semantics to compare two instances. (And because x and y are boxed, they are represented in separate memory locations, and so are unequal.) The virtual Equals method, however, defers to the Int32 type’s Equals method, which uses value-type semantics in comparing two values.

The static object.Equals method simply calls the virtual Equals method on the first argument—after checking that the arguments are not null:

object x = null, y = 3;
bool error = x.Equals (y);        // Runtime error!
bool ok = object.Equals (x, y);   // OK (false)

ReferenceEquals forces a reference-type equality comparison (this is occasionally useful on reference types where the == operator has been overloaded to do otherwise).

GetHashCode emits a hash code suitable for use with hashtable-based dictionaries, namely System.Collections.Generic.Dictionary and System.Collections.Hashtable.

To customize a type’s equality semantics, you must at a minimum override Equals and GetHashCode. You would also usually overload the == and != operators. For an example of how to do both, see Operator Overloading.

The ToString Method

The ToString method returns the default textual representation of a type instance. The ToString method is overridden by all built-in types:

string s1 = 1.ToString();      // s1 is "1"
string s2 = true.ToString();   // s2 is "True"

You can override the ToString method on custom types as follows:

public override string ToString() { return "Foo"; }
..................Content has been hidden....................

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