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; } publicobject
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.
In the section Generics, we’ll describe how to
improve our Stack
class to better
handle stacks with same-typed elements.
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
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
.
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.
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();
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
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"; }