System.Object

 
Chapter 5 - C# and the Base Classes
bySimon Robinsonet al.
Wrox Press 2002
  

In Chapter 3, we have touched on System.Object . We indicated that it is the universal base class from which everything else is inherited, and listed its main member methods. However, in that chapter we hadn't yet learned sufficient C# to be able to understand the significance of all of the methods , and as a result, the only System.Object methods that we were able to look at in any detail were ToString() and Finalize() (implemented as a destructor in C# ) . As we work through this chapter, we will need to work with many of the remaining System.Object methods. In this section, we will provide a brief summary of them, and look in detail at the various ways of testing for equality.

To recap, below we reproduce the table from Chapter 3 that lists the methods available in System.Object :

Method

Access

Purpose

string ToString()

public virtual

Returns a string representation of the object

int GetHashCode()

public virtual

Returns a hash of the object designed to allow you to efficiently look up instances of the object in dictionaries

bool Equals(object obj)

public virtual

Compares this object with another instance of the class for equality

bool Equals(object objA, object objB)

public static

Compares instances of the class for equality

bool ReferenceEquals(object objA, object objB)

public static

Compares object references to see if they refer to the same object

Type GetType()

public

Returns an object derived from System.Type that can give details of a data type

object MemberwiseClone()

protected

Makes a shallow copy of the object (in other words, copies data in the object but not other objects any fields refer to)

void Finalize()

protected virtual

Destructor

Four of these methods are declared as virtual and are therefore available for you to override. In all cases, however, good programming practice places restrictions on how you should implement your overrides .

We will note the following about the System.Object members :

  • ToString() This is intended as a fairly basic, quick and easy string representation, and is used for those situations when you just want a quick idea of the contents of an object, perhaps for debugging. If you need a more sophisticated string representation that, for example, takes account of the culture (the locale) and any requests that client code makes to have the object represented in a particular format, then you should implement the IFormattable interface, which we'll cover later in this chapter. For example, dates can be expressed in a huge variety of different formats, but DateTime.ToString() does not offer you any choice about format.

  • 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, then you will need to override GetHashCode() . There are some fairly strong requirements for how you implement your overload, and we deal with these later in this chapter in the Dictionaries section.

  • Equals() (both versions) and ReferenceEquals() There are subtle differences between how these three methods, along with the comparison operator, == , are intended to be used. There are also restrictions 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. We will explore these issues in this chapter.

  • Finalize() We covered this method in Chapter 3. It is intended as the destructor, 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, but this is not true of any overrides. You should override it only when necessary, for example if your object uses external resources such as file or database connections. If you do need to override it, then you should provide a Close() or Dispose() method for clients to use too. Note that value types are not garbage-collected , so for these types there is no point overriding Finalize() . Overriding Finalize() in C# is not done explicitly (as this causes a compilation error), but implicitly, by supplying a destructor. The destructor is converted by the compiler into a Finalize() method.

  • 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 . We will examine this area in detail later in the chapter in the Reflection section.

  • MemberwiseClone() This is the only member of System.Object that we don't examine in detail anywhere in the book. There is no need to, since 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.

Comparing Reference Objects for Equality

One aspect of System.Object that can look surprising at first sight is the fact that it defines three different ways of comparing objects for equality. Add to this the comparison operator, and we actually have four ways of comparing for equality. Why so many? There are in fact some subtle differences between the different methods, which we will now examine. We will look first at the case where we are comparing reference types.

ReferenceEquals()

ReferenceEquals() is exactly what it says. It is there to test whether two references refer to the same instance of a class: whether the two references contain the same address in memory. As a static method, it is not possible to override it, so the System.Object implementation is what you always have. ReferenceEquals() will always return true if supplied with two references refer to the same object instance, and false otherwise . It does, however, consider null to be equal to null :

   SomeClass x, y;     x = new SomeClass();     y = new SomeClass();     bool B1 = ReferenceEquals(null, null);     // returns true     bool B2 = ReferenceEquals(null,x);         // returns false     bool B3 = ReferenceEquals(x, y);           // returns false because x and y     // point to different objects   

Virtual Equals() Method

The virtual, instance version of Equals() can be seen pretty much as the opposite of ReferenceEquals() . While it is true that the System.Object implementation of Equals() works by comparing references, this method is provided in case you wish to override it to compare the values of object instances. In particular, if you intend your instances of your class to be used as keys in a dictionary, then you will need to override this method to compare values. Otherwise, depending on how you override GetHashCode() , the dictionary class that contains your objects will either not work at all, or will work very inefficiently. One point you should note when overriding Equals() is that your override should never throw exceptions. Once again, this is because doing so could cause problems for dictionary classes and possibly certain other .NET base classes that internally call this method.

We will show an example of how to override this method in the MortimerPhonesEmployees example later in this chapter, in the section on dictionaries (hash tables).

Static Equals() Method

The static version of Equals() actually does the same thing as the virtual, instance version. The difference is that the static version of this method is able to cope when either of the objects is null , and therefore, provides an extra safety guard against throwing exceptions if there is a risk that an object might be null . The static overload first checks whether the references it has been passed are null . If they are both null , then it returns true (since null is considered to be equal to null ). If just one of them is null , then it returns false . If both references actually refer to something, then it calls the virtual, instance version of Equals() . This means that when you override the instance version of Equals() , the effect is as if you were overriding the static version as well.

Comparison Operator (==)

The comparison operator can be best seen as an intermediate version between strict value comparison and strict reference comparison. In most cases, writing:

   bool b = (x == y);   // x, y object references   

should mean that you are comparing references. However, it is accepted that there are some classes whose meanings make more intuitive sense if they are treated as values. In those cases, it is better to override the comparison operator to perform a value comparison. The obvious example of this is for strings for which Microsoft has overridden this operator, because when developers think of performing string comparisons, they are almost invariably thinking of comparing the contents of the strings rather than the references.

One point to watch for if you are overriding comparison functions and Object.GetHashCode() is that there are certain guidelines about good coding practice, and the compiler will actually flag a warning if you don't adhere to the following guidelines:

  1. You shouldn't override just one of Object.Equals() or Object.GetHashCode() . If you need to override one of these methods, then consider overriding the other one as well. This is because dictionary implementations require both methods to act in a consistent way (if Object.Equals() does value comparisons, then GetHashCode() should construct a code based on the value too). Having said this though, you might ignore this guideline if you know your class will never be used as a dictionary key (more on that later in the chapter).

    At the time of writing, overriding GetHashCode() without overriding Equals() doesn't generate a compiler warning, but you should still be careful about doing that doing so will most likely prevent dictionaries based on your class from working.

  2. If you override the == operator, then you ought to override Object.Equals() (and hence, from the first rule, Object.GetHashCode() ). This is because the expectation of client code will be that, if == does a value comparison, then Object.Equals() will do a value comparison too.

If you get a compiler warning because you are not following these guidelines, then you need to make sure you know what you are doing and you have a good reason for not following them.

By the way, don't be tempted to overload the comparison operator by calling the one-parameter version of Equals() . If you do that, and somewhere in your code an attempt is made to evaluate (objA == objB) when objA happens to be null , you will get an instant exception as the .NET runtime tries to evaluate null.Equals(objB)! Working the other way round (overriding Equals() to call the comparison operator) should be safe.

Comparing Value Types for Equality

When comparing value types for equality, the same principles hold as for reference types: ReferenceEquals() is used to compare references, Equals() is intended for value comparisons, and the comparison operator is viewed as an intermediate case. However the big difference here is that value types need to be boxed in order to convert them to references, and that Microsoft has in fact already overloaded the instance Equals() method in the System.ValueType class in order to provide meanings more appropriate to value types. If you call sA.Equals(sB) where sA and sB are instances of some struct, then the return value will be true or false according to whether sA and sB contain the same values in all their fields. On the other hand, no overload of == is available by default for your own structs. Writing (sA == sB) in any expression will result in a compilation error unless you have provided an overload of == in your code for the struct in question.

Another point is that ReferenceEquals() will always return false when applied to value types, because in order to call this method, the value types will need to be boxed into objects. Even if you write:

   bool b = ReferenceEquals(v,v);   // v is a variable of some value type   

you will still get the answer of false because v will be boxed separately when converting each parameter, which will mean you get different references! Calling ReferenceEquals() to compare value types doesn't really make much sense.

Although the default override of Equals() supplied by System.ValueType will almost certainly be adequate for the vast majority of structs that you define, you may wish to override it again for your own structs in order to improve performance. Also, if a value type contains reference types as fields, you may wish to override Equals() to provide appropriate semantics for these fields, as the default override of Equals() will simply compare their addresses.

  


Professional C#. 2nd Edition
Performance Consulting: A Practical Guide for HR and Learning Professionals
ISBN: 1576754359
EAN: 2147483647
Year: 2002
Pages: 244

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net