The object Type

object (System.Object) is the ultimate base class for all types. Any type can be 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 LIFO——“Last in, First out.” 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 obj)
	    { data[position++] = obj; }

	  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");

	// Explicit cast is needed because we're downcasting:
	string s = (string) stack.Pop();

	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, and so be added to our stack. This feature of C# is called type unification:

	stack.Push (3);
	int three = (int) stack.Pop();

When you cast between a value type and object, the CLR must perform some special work to bridge the difference in semantics between value and reference types. This process is called boxing and unboxing.

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 (which we will visit later). 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 (exactly) 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;           // inferred type is double
	int x = (int) (double) obj; // x is now 3

In the last example, (int) performs a conversion; (double) performs an unboxing.

Copying semantics of boxing and unboxing

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

Static and Dynamic Type Checking

C# checks types both statically and dynamically.

Static type checking occurs at compile time. 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";

Dynamic type checking occurs at runtime. Whenever an unboxing or downcast occurs, the runtime checks the type dynamically. For example:

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

Dynamic 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.

Object Member Listing

Here are all the members of object:

	public class Object
	{
	  public 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();
	}

GetType()and typeof

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.

  • Use the typeof operator on a type name.

GetType is evaluated dynamically 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 17 of C# 3.0 in a Nutshell.

Equals, ReferenceEquals, and GetHashCode

The Equals method 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—after checking that the arguments are not null.

	object x = null;
	object y = 3;
	bool error = x.Equals (y);    // NullReferenceException
	bool ok = object.Equals (x, y);

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 when the type is used in a hashtable-based dictionary, 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 on how to do both, see the upcoming “Operator Overloading” section.

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. Here is an example of using the int type’s ToString method:

	int x = 1;
	string s = x.ToString();      // s is "1"

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

	public class Panda
	{
	  public string Name;
	  public override string ToString() { return Name; }
	}
	...

	Panda p = new Panda { Name = "Petey" };
	Console.WriteLine (p);    / Petey

Note

When you call an overridden object member such as ToString directly on a value type, boxing doesn’t occur— boxing occurs only when you cast.

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

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