1.
System.Object
The runtime requires every type to ultimately be derived from
the System.Object type.
Because all types are ultimately derived from System.Object, you
are guaranteed that every object of every type has a minimum set of
methods. Specifically, the System.Object class offers
the public instance methods
listed in below.
Equals
GetHashCode
ToString
GetType
In addition, types that derive from System.Object have access to
the protected methods listed
in below.
MemberwiseClone
Finalize
The CLR requires all objects to be created using
the new operator.
2.
new
Here’s what
the new operator
does:
1.
It calculates the number of bytes
required by all instance fields defined in the type
and all of its base types up to and including System.Object (which defines
no instance fields of its own). Every object on the heap requires some
additional members—called the type object pointer and the sync
block index—used by the CLR to manage the object. The bytes for
these additional members are added to the size of the object.
2.
It allocates memory for the object by allocating the number of bytes required
for the specified type from the managed
heap; all of these bytes are then set
to zero (0).
3.
It initializes the object’s type object pointer and sync block index
members.
4.
The type’s instance constructor is called, passing it any arguments (the
string "ConstructorParam1" in the preceding example) specified in the call
to new. Most compilers automatically emit code in a constructor to call a
base class’s constructor. Each constructor is responsible for initializing
the instance fields defined by the type whose constructor is being called.
Eventually, System.Object’s constructor is called, and this
constructor method does nothing but return. You can verify this by
using ILDasm.exe to load MSCorLib.dll and examine System.Object’s
constructor method.
3.
Casting Between Types
One of the most important features of the CLR is
type safety. At runtime, the CLR
always knows what type an object is. You can always discover an object’s
exact type by calling
the GetType method. Because this method
is nonvirtual, it is impossible for a type to spoof another type.
The CLR allows you to cast an object to its type or to
any of its base types.
C# doesn’t require any special syntax to cast an object to any of its
base types, because casts to base types are considered safe
implicit conversions. However, C# does require the developer
to explicitly cast an object to any of its derived
types since such a cast could fail at runtime.
System.InvalidCastException
4.
Castring with the
C# as and is Operators
The is operator checks whether
an object
is compatible with a given
type, and the result of the
evaluation is a Boolean: true or false.
The is operator will never throw an
exception.
The is operator is typically used as follows:
if (o is Employee) {
Employee e = (Employee) o;
// Use e within the remainder of the ‘if‘
statement.
}
The CLR’s type checking improves security,
but it certainly comes at a performance cost,
because the CLR must determine the actual type of the object referred to by
the variable (o), and then the CLR must walk the inheritance hierarchy,
checking each base type against the specified type (Employee). Because this
programming paradigm is quite common, C# offers a way to simplify this
code and improve its performance by providing
an as operator:
Employee e = o as Employee;
if (e != null) {
// Use e within the ‘if‘ statement.
}
Notice that the as operator causes the CLR to verify an
object’s type just
once. The as
operator works just
as casting does except that
the as operator will never throw an
exception. Instead, if the object can’t be cast, the result
is null.
5.
namespaces and assemblies
using namespace;
Be aware that a namespace and an assembly (the file that implements
a type) aren’t necessarily related. In particular, the various types
belonging to a single namespace might be implemented in multiple
assemblies.
6.
How things relate at Runtime
Stack space is used for passing
arguments to a method and for local variables defined within a method.
All
but the simplest of methods contain some prologue
code, which initializes a method before it can start doing its
work. These methods also contain epilogue
code, which cleans up a method after it has performed its work so
that it can return to its caller.
When a method is called, the arguments will be pushed on the stack
firstly, then the address indicating where the called method should
return to in the calling method is pushed on the stack.
The Progress:
(1) As the just-in-time (JIT) compiler converts method‘s
Intermediate Language (IL) code into native CPU instructions, it notices
all of the types that are referred to inside the method. At this
time, the CLR ensures that the assemblies that
define these types are loaded. Then, using the
assembly’s metadata, the CLR extracts information about these types and
creates some data structures to represent
the types themselves.
(2) All objects on the heap contain two overhead
members: the type object pointer and the
sync block index.
(3) When you define a type, you can define static data fields
within it. The bytes that back
these static data fields are
allocated within the type
objects themselves. Finally, inside each
type object is a method table with one entry per method defined within the
type.
(4) Whenever a new
object is created on the heap,
the CLR automatically initializes the
internal type object
pointer member to refer
to the object’s
corresponding type object.
(5) Furthermore,
the CLR initializes
the sync block
index and sets all
of the object’s instance fields to null or
0 (zero) prior to calling the type’s constructor, a
method that will likely modify some of the instance data
fields.
(6) When calling a static method,
the JIT
compiler locates the type object
that corresponds to the type that defines the
static method. Then,
the JIT compiler
locates the entry in the type
object’s method table that
refers to the method being called, JITs the
method (if necessary),
and calls the JITted code.
(7) When calling a nonvirtual
instance method, the JIT compiler
locates the type
object that corresponds to the type of the
variable being used to make the call. JIT compiler walks
down the class hierarchy
toward Object looking for this method. It can
do this because each type object has a field in it that refers to its base
type. Then, the JIT compiler locates the entry in the type
object’s method table that refers to the method being called, JITs the
method (if necessary), and then calls the JITted code.
(8) When calling a virtual
instance method, the JIT compiler produces
some additional code in the method, which will be executed each
time the method is invoked. This code will first
look in the variable being used to make the call
and then follow the address to the calling
object. Then, the
code will examine the
object’s internal type object
pointer member; this member refers to the actual
type of the object. The
code then locates the
entry in the type object’s method
table that refers to the method being called, JITs the method (if
necessary), and calls the JITted code.
(9) Type objects are actually
objects themselves, so they all
contail type object pointer. When the CLR
creates type objects, the CLR must initialize these members.
(10) When the CLR starts running in
a process, it immediately creates a special
type object for the System.Type type (defined in
MSCorLib.dll).
(11) System.Type type
object is an object itself and therefore also has a
type object pointer member in it, and it is
logical to ask what this member refers to. It
refers to itself because the System.Type type
object is itself an “instance” of a type object.
(12) By the
way, System.Object’s GetType method
simply returns the address stored in the specified
object’s type object pointer member. In other words,
the GetType method returns a pointer to
an object’s type object, and this is how you can
determine the true type of any
object in the system (including type
objects).