The Unified Type System
When we define an object, we must specify its type. The type determines the kind of values the object can hold and the permissible range of those values. For example, byte is an unsigned integral type with a size of 8 bits. The definition
byte b;
declares that b can hold integral values, but that those values must be within the range of 0 to 255. If we attempt to assign b a floating-point value:
b = 3.14159; // compile-time error
a string value:
b = "no way"; // compile-time error
or an integer value outside its range:
b = 1024; // compile-time error
each of those assignments is flagged as a type error by the compiler. This is true of the C# array type as well. So why is an ArrayList container able to hold objects of any type?
The reason is the unified type system. C# predefines a reference type named object. Every reference and value type—both those predefined by the language and those introduced by programmers like us—is a kind of object. This means that any type we work with can be assigned to an instance of type object. For example, given
object o;
each of the following assignments is legal:
o = 10;
o = "hello, object";
o = 3.14159;
o = new int[ 24 ];
o = new WordCount();
o = false;
We can assign any type to an ArrayList container because its elements are declared to be of type object.
object provides a fistful of public member functions. The most frequently used method isToString(), which returns a string representation of the actual type—for example,
Console.WriteLine( o.ToString() )
C# provides a "unified type system". All types — including value types — derive from the type object
.
It is possible to call object methods on any value, even values of "primitive" types such as int
.
The example
using System;
class Test
{
static void Main() {
Console.WriteLine(3.ToString());
}
}
calls the object
-defined ToString
method on an integer literal, resulting in the output "3
".
The example
class Test
{
static void Main() {
int i = 123;
object o = i; // boxing
int j = (int) o; // unboxing
}
}
is more interesting. An int
value can be converted to object
and back again to int
. This example shows both boxing and unboxing. When a variable of a value type needs to be converted to a reference
type, an object box is allocated to hold the value, and the value is copied into the box.
Unboxing is just the opposite. When an object box is cast back to its original value type, the value is
copied out of the box and into the appropriate storage location.
This type system unification provides value types with the benefits of object-ness
without introducing unnecessary overhead. For programs that don't need int
values to act like objects,
int
values are simply 32-bit values. For programs that need int
values to behave like objects, this
capability is available on demand. This ability to treat value types as objects bridges the gap between
value types and reference types that exists in most languages. For example, a Stack
class can provide
Push
and Pop
methods that take and return object
values.
public class Stack
{
public object Pop() {...}
public void Push(object o) {...}
}
Because C# has a unified type system, the Stack
class can be used with elements of any type,
including value types
like int
.
No comments:
Post a Comment