4.3. Implementing System.Object Methods in a Custom Class

 < Day Day Up > 

When creating a custom class, particularly one that will be available to other developers, it is important to ensure that it observes the proper rules of object etiquette. It should be CLS compliant, provide adequate exception handling, and adhere to OOP principles of encapsulation and inheritance when providing member accessibility. A class should also implement the features that .NET developers are accustomed to when working with Framework classes. These features include a custom implementation of the System.Object methods that all classes inherit:

  • ToString(). By default, this method returns the name of the class. It should be overridden to display contents of the object that distinguish it from other instances of the class.

  • Equals(). This is used to compare instances of a class to determine if they are equal. It's up to the class to define what equality means. This could mean that two objects have matching field values or that two objects reference the same memory location.

  • MemberwiseClone(). This method returns a copy of an object that contains the object's value fields only referred to as a shallow copy. A custom class can use this method to return a copy of itself or implement its own clone method to return a deep copy that also includes reference type values.

The remainder of this section provides examples of overriding or using these methods in custom classes. Note that System.Object.Finalize a method to perform cleanup duties before an object is claimed by garbage collection is discussed in Section 4.6, "Object Life Cycle Management."

ToString() to Describe an Object

This method is most commonly used with primitive types to convert numbers to a string format that can be displayed or printed. When used with objects, the default is to return the fully qualified name of the object: <namespace>.<classname>. It's common to override this and return a string containing selected values from members of the object. A good example of this is the exception object from the previous section (refer to Figure 4-3) that returns the Message and StackTrace values:

 ex.ToString()   // Output:                 //  Attempted to divide by zero                 //  at TestExcep.Calc(Int32 j)                 //  at MyApp.Main() 

The code shown in Listing 4-5 demonstrates how easy it is to implement ToString in your own class.

The StringBuilder class is used to create the text string returned by the method. It provides an efficient way of handling strings and is described in Chapter 5, "C# Text Manipulation and File I/O."

Listing 4-5. Overriding ToString()
 using System.Text; using System; public class Chair {    private double myPrice;    private string myVendor, myID;    public Upholstery myUpholstery;    public  Chair(double price, string vendor, string sku)    {  myPrice = price;       myVendor = vendor;       myID = sku;    }    // Override System.Object ToString()    public override string ToString()    {       StringBuilder chairSB = new StringBuilder();       chairSB.AppendFormat("ITEM = Chair");       chairSB.AppendFormat(" VENDOR = {0}", this.myVendor);       chairSB.AppendFormat(" PRICE = {0}",                            this.myPrice.ToString());       return chairSB.ToString();    }    public string MyVen    {       get {return myVendor;}    }    //... other properties to expose myPrice, myID } public class Upholstery {    public string Fabric ;    public Upholstery( string fab)    { Fabric = fab; } } 

The following statements create an instance of the object and set desc to the more meaningful value returned by ToString():

 Chair myChair = new Chair(120.0, "Broyhill", "60-1222"); string desc = myChair.ToString()); // Returns ITEM = Chair VENDOR = Broyhill PRICE = 120.0 

Equals() to Compare Objects

When comparing two reference types, Equals returns true only if they point to the same object in memory. To compare objects based on their value (value equality rather than referential equality), you must override the default method. An example of this is the String class a reference type that implements Equals to perform a value comparison based on the characters in the strings.

The code in Listing 4-6 illustrates how to override Equals in the Chair class to compare objects using value semantics. It also overrides the GetHashCode method always recommended when overriding Equals.

Listing 4-6. Overriding Equals()
 public override bool Equals(Object obj) {    // Include the following two statements if this class    // derives from a class that overrides Equals()    //if (!base.Equals(obj))    //   return false;    // (1) Null objects cannot be compared    if (obj == null) return false;    // (2) Check object types    if (this.GetType() != obj.GetType()) return false;    // (3) Cast object so we can access its fields    Chair otherObj = (Chair) obj;    // (4) Compare reference fields    if (!Object.Equals(myUpholstery,               otherObj.myUpholstery)) return false;    // (5) Compare Value Type members    if (!myVendor.Equals(otherObj.myVendor)) return false;    if (!myPrice.Equals(otherObj.myPrice))   return false;    if (!myID.Equals(otherObj.myID))         return false;    return true; } // Override GetHashCode   Required if Equals overridden public override int GetHashCode() {    return myID.GetHashCode(); } 

This method compares an instance of the current object with the one passed to it. The first step is to ensure that the received object is not null. Next, following the steps in Figure 4-5, the types of the two objects are compared to make sure they match.

Figure 4-5. Steps in overriding Equals()

The heart of the method consists of comparing the field values of the two objects. To compare reference fields, it uses the static Object.Equals method, which takes two objects as arguments. It returns true if the two objects reference the same instance, if both objects are null, or if the object's Equals comparison returns true. Value types are compared using the field's Equals method:

 if (!myID.Equals(otherObj.myID)) return false; 

Here is an example that demonstrates the new Equals method. It creates two Chair objects, sets their fields to the same value, and performs a comparison:

 Chair myChair = new Chair(150.0, "Lane", "78-0988"); myChair.myUpholstery = new Upholstery("Silk"); Chair newChair = new Chair(150.0, "Lane", "78-0988"); newChair.myUpholstery= new Upholstery("Silk"); // Next statement returns false. Why? bool eq = ( myChair.Equals(newChair)); 

Although the two objects have identical field values, the comparison fails which is probably not what you want. The reason is that the objects point to two different myUpholstery instances, causing their reference comparison to fail. The solution is to override the Equals method in the Upholstery class, so that it performs a value comparison of the Fabric fields. To do so, place this code inside its Equals method, in addition to the other overhead code shown in Listing 4-6:

 Upholstery otherObj = (Upholstery) obj; if (!Fabric.Equals(otherObj.Fabric)) return false; 

Overriding GetHashCode

The GetHashCode method generates an Int32 hash code value for any object. This value serves as an identifier that can be used to place any object in a hash table collection. The Framework designers decreed that any two objects that are equal must have the same hash code. As a result, any new Equals method must be paired with a GetHashCode method to ensure that identical objects generate the same hash code value.

Ideally, the hash code values generated over the range of objects represent a wide distribution. This example used a simple algorithm that calls the base type's GetHashCode method to return a value based on the item's ID. This is a good choice because the IDs are unique for each item, the ID is an instance field, and the ID field value is immutable being set only in the constructor.

Determining If References Point to the Same Object

After you override the original Equals method to compare object values, your application may still need to determine if reference variables refer to the same object. System.Object has a static method called ReferenceEquals for this purpose. It is used here with our Chair class:

 Chair chair1 = new Chair(120.0, "Broyhill", "66-9888") ) Chair chair2 = new Chair(120.0, "Broyhill", "66-9933") ) Chair chair3 = chair1; if (Object.ReferenceEquals(chair1, chair3) )    { MessageBox.Show("Same object");} else    { MessageBox.Show("Different objects");} 

The method returns true because chair1 and chair3 reference the same instance.

Cloning to Create a Copy of an Object

The usual way to create an object is by using the new operator to invoke a constructor. An alternative approach is to make a copy or clone of an existing object. The object being cloned may be a class instance, an array, a string, or any reference type; primitives cannot be cloned. For a class to be cloneable, it must implement the ICloneable interface. This is a simple interface defined as

 public interface ICloneable {    Object Clone(); } 

It consists of a single method, Clone, that is implemented to create a copy of the object. The cloned object may represent a shallow copy or a deep copy of the object's fields. A shallow copy creates a new instance of the original object type, and then copies the non-static fields from the original object to the new object. For a reference type, only a pointer to the value is copied. Thus, the clone points to the same reference object as the original. A deep copy duplicates everything. It creates a copy of reference objects and provides a reference to them in the copy.

Figure 4-6 depicts a shallow and deep copy for this instance of the Chair class:

 Chair myChair = new Chair(150.0, "Lane", "78-0988"); Upholstery myFabric = new Upholstery("Silk"); myChair.myUpholstery = myFabric; 

Figure 4-6. Comparison of shallow and deep copy

In both cases, the clone of the myChair object contains its own copy of the value type fields. However, the shallow copy points to the same instance of myUpholstery as the original; in the deep copy, it references a duplicate object.

Let's now look at how to implement shallow cloning on a custom object. Deep cloning (not discussed) is specific to each class, and it essentially requires creating an instance of the object to be cloned and copying all field values to the clone. Any reference objects must also be created and assigned values from the original referenced objects.

How to Create a Shallow Copy

A shallow copy is sufficient when the object to be copied contains no reference type fields needed by the copy. The easiest way to implement shallow copying is to use the System.Object.MemberwiseClone method, a protected, non-virtual method that makes a shallow copy of the object it is associated with. We use this to enable the Chair class to clone itself:

 public class Chair: ICloneable {    //... other code from Listing 4-5    public Object Clone()    {       return MemberwiseClone(); // from System.Object    } 

The only requirements are that the class inherit the ICloneable interface and implement the Clone method using MemberwiseClone. To demonstrate, let's use this code segment to create a shallow copy clone of myChair by calling its Clone method:

 // Make clone of myChair Chair chairClone = (Chair)myChair.Clone(); bool isEqual; // (1) Following evaluates to false isEqual = Object.ReferenceEquals(myChair,chairClone); // (2) Following evaluates to true isEqual = Object.ReferenceEquals(           myChair.myUpholstery,chairClone.myUpholstery); 

The results confirm this is a shallow copy: The reference comparison of myChair and its clone fails because chairClone is created as a copy of the original object; on the other hand, the comparison of the reference field myUpholstery succeeds because the original and clone objects point to the same instance of the myUpholstery class.

     < Day Day Up > 

    Core C# and  .NET
    Core C# and .NET
    ISBN: 131472275
    EAN: N/A
    Year: 2005
    Pages: 219

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