The Object Class
As indicated earlier, all .NET classes are
ultimately derived from System.Object.
In fact, if you don’t specify a base class when you define a class,
the compiler will automatically assume that it derives from
Object. Because inheritance has not been
used in this chapter, every class you have seen here is actually
derived from System.Object. (As noted
earlier, for structs this derivation is indirect: A struct is
always derived from System.ValueType,
which in turn derives from System.Object.)
The practical significance of this is that, besides
the methods and properties and so on that you define, you also have
access to a number of public and protected member methods that have
been defined for the Object class. These
methods are available in all other classes that you define.
System.Object Methods
The methods defined in Object are as shown in the following table:
You haven’t yet seen enough of the C# language to
be able to understand how to use all these methods. For the time
being, the following list simply summarizes the purpose of each
method, with the exception of ToString(), which is examined in more detail.
-
ToString() - This
is intended as a fairly basic, quick-and-easy string
representation; use it when you just want a quick idea of the
contents of an object, perhaps for debugging purposes. It provides
very little choice of how to format the data: For example, dates
can in principle be expressed in a huge variety of different
formats, but DateTime.ToString() does
not offer you any choice in this regard. If you need a more
sophisticated string representation that, for example, takes
account of your formatting preferences or of the culture (the
locale), then you should implement the IFormattable interface (see Chapter
8, “Strings and Regular Expressions”).
-
GetHashCode() -
This is used if objects are placed in a data structure known as a
map (also known as a hash table or dictionary). It is used by
classes that manipulate these structures in order to determine
where to place an object in the structure. If you intend your class
to be used as key for a dictionary, you will need to override
GetHashCode(). Some fairly strict
requirements exist for how you implement your overload, and you
learn about those when you examine dictionaries in Chapter
10, “Collections.”
-
Equals() (both versions) and ReferenceEquals() - As you’ll gather by the
existence of three different methods aimed at comparing the
equality of objects, the .NET Framework has quite a sophisticated
scheme for measuring equality. Subtle differences exist between how
these three methods, along with the comparison operator,
==, are intended to be used. Not only
that, but restrictions also exist on how you should override the
virtual, one-parameter version of Equals() if you choose to do so, because certain
base classes in the System.Collections
namespace call the method and expect it to behave in certain ways.
You explore the use of these methods in Chapter 6, “Operators
and Casts,” when you examine operators.
-
Finalize() - This
method is covered in Chapter 11, “Memory Management and
Pointers.” It is intended as the nearest that C# has to
C++-style destructors and is called when a reference object is
garbage collected to clean up resources. The Object implementation of Finalize() actually does nothing and is ignored by
the garbage collector. You will normally override Finalize() if an object owns references to unmanaged
resources that need to be removed when the object is deleted. The
garbage collector cannot do this directly because it only knows
about managed resources, so it relies on any finalizers that you
supply.
-
GetType() - This
method returns an instance of a class derived from System.Type. This object can provide an extensive
range of information about the class of which your object is a
member, including base type, methods, properties, and so on.
System.Type also provides the entry
point into .NET’s reflection technology. Chapter 12, “Reflection,” examines this topic.
-
MemberwiseClone()
- This is the only member of System.Object that isn’t examined in detail anywhere
in the site. There is no need to, because it is fairly simple in
concept. It simply makes a copy of the object and returns a
reference (or in the case of a value type, a boxed reference) to
the copy. Note that the copy made is a shallow copy - this means
that it copies all the value types in the class. If the class
contains any embedded references, then only the references will be
copied, not the objects referred to. This method is protected and
so cannot be called to copy external objects. It is also not
virtual, so you cannot override its implementation.
The ToString() Method
You’ve already encountered ToString() in Chapter 2, “C#
Basics.” It provides the most convenient way to get a quick
string representation of an object.
For example:
Here’s another example:
Object.ToString() is
actually declared as virtual, and all these examples are taking
advantage of the fact that its implementation in the C# predefined
data types has been overridden for us in order to return correct
string representations of those types. You might not think that the
Colors enum counts as a predefined data
type. It actually gets implemented as a struct derived from
System.Enum, and System.Enum has a rather clever override of
ToString() that deals with all the enums
you define.
If you don’t override ToString() in classes that you define, your classes
will simply inherit the System
.Object implementation - which displays
the name of the class. If you want ToString() to return a string that contains
information about the value of objects of your class, then you will
need to override it. To illustrate this, the following example,
Money, defines a very simple class, also
called Money, which represent U.S.
currency amounts. Money simply acts as a
wrapper for the decimal class but supplies a ToString() method. Note that this method must be
declared as override because it is
replacing (overriding) the ToString()
method supplied by Object. Chapter
4 discusses overriding in more detail. The complete code for
this example is as follows. Note that it also illustrates use of
properties to wrap fields:
This example is here just to illustrate syntactical
features of C#. C# already has a predefined type to represent
currency amounts, decimal, so in real
life, you wouldn’t write a class to duplicate this functionality
unless you wanted to add various other methods to it. And in many
cases, due to formatting requirements, you’d probably use the
String.Format() method (which is covered
in Chapter 8) rather than ToString() to display a currency string.
In the Main() method,
you first instantiate a Money object.
The ToString() method is then called,
which actually executes the override version of the method. Running
this code gives the following results:
|