Working with System.Object


All classes within the .NET Framework ultimately derive from .NET's root class System.Object. If we do not inherit from another class, the class will implicitly inherit from System.Object. The compiler enforces this requirement, which means that we don't need to write:

      public class Account: System.Object 

because it is implicit. As all classes derived from System.Object either directly or indirectly, it is quite easy to forget the functionality that System.Object provides. Several of the members of System.Object are virtual methods. You can override these virtual methods to provide our own implementations. Overriding System.Object's members makes classes more consistent with those found in the .NET base class library set. The vast majority of the .NET classes override System.Object's methods to provide functionality tailored to the class. The following System.Object methods are commonly implemented in classes:

  • ToString()

  • Equals(object)

  • GetHashCode()

In the following section, we will explore how each of these functions works and how we can override them in our Account class. But first, let's examine another very useful method provided as part of System.Object.

GetType()

A very straightforward, and rather useful, member of the System.Object class is the GetType() method. Consider the following program:

    using System;    public class Account    {    // Code omitted for brevity    }    class AtTheBank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount= new Account();        Console.WriteLine("myAccount is an object of type : " +                           myAccount.GetType());      }    } 

Compiling and running the above code produces the following output:

    C:\Class Design\Ch 02>sysobject_gettype_bankaccount.exe    myAccount is an object of type : Account 

As you can see, GetType() returns the type of the instance. In this case, the returned value is Account as myAccount is an instance of the Account object.

ToString()

ToString() is typically the default class member. For example, if we have a function call as below:

      Console.WriteLine(Object); 

it is equivalent to:

      Console.Writeline(Object.ToString()); 

This is because, if you don't specify a class member then it defaults to a call to ToString(). This call returns a string representation of the current object. By default, the System.Object.ToString() method will only return the instance's type name. The following code is located in sysobject_tostring_bankaccount.cs:

    using System;    public class Account    {      private double balance;      public Account(double startingBalance)      {        balance=startingBalance;      }    }    class AtTheBank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount= new Account(500);        Console.WriteLine("ToString:" + myAccount.ToString());        Console.WriteLine("Same as above:" + myAccount);      }    } 

Compiling and running the above code results in the following output:

    C:\Class Design\Ch 02>sysobject_tostring_bankaccount.exe    ToString:Account    Same as above:Account 

The majority of the time, the default implementation of ToString() is not very useful. After all, we can retrieve the same information by calling GetType(). With the default ToString() method, the same results are returned for every instance of the class, regardless of the instance state of the object. What we need is a descriptive string that is particular to the object instance. Overriding System.Object.ToString() allows us to customize the string representation for a particular instance, as can be seen in sysobject_tostring_bankaccount2.cs, below:

    using System;    public class Account    {      private double balance;      public Account(double startingBalance)      {        balance=startingBalance;      }      public override string ToString()      {        return "Object: " + GetType() + " balance: " + balance;      }    }    class AtTheBank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount= new Account(500);        Console.WriteLine("ToString:" + myAccount.ToString());      }    } 

Compiling and running the above code provides the following output:

    C:\Class Design\Ch 02>sysobject_tostring_bankaccount2.exe    ToString:Object: Account balance: 500 

Equals()

Quite often we need the ability to compare objects for equality. System.Object.Equals() gives us such ability. .NET defines two forms of equality: reference equality and value equality. By default, System.Object.Equals() will test for reference equality. Reference equality occurs when two references point to the same underlying object. The following example, sysobject_equals_bankaccount.cs, illustrates this:

    using System;    public class Account    {      private double balance;      public Account(double startingBalance)      {        balance=startingBalance;      }    }    class atthebank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount = new Account(500);        Account mySavings = new Account(500);        Console.WriteLine(myAccount.Equals(mySavings));        mySavings=myAccount;        Console.WriteLine(myAccount.Equals(mySavings));      }    } 

The first test of equality returns a false. While mySavings and myAccount both have the same value, they do not reference the same instance. After setting mySavings to myAccount, the next test of equality returns true. The resulting output is:

    C:\Class Design\Ch 02>sysobject_equals_bankaccount.exe    False    True 

Many real-world objects need the ability to test for value equality. Value equality is satisfied if two objects have the same state. Overriding the System.Object.Equals() method allows us to perform a custom value equality test. The following code, saved as sysobject_equals_bankaccount2.cs, provides an example for overriding the default Equals() implementation:

    using System;    public class Account    {      private double balance;      public Account(double startingBalance)      {        balance=startingBalance;      } 

In the first two statements below, we perform a check to ensure that we were not passed a null object and that the object we were passed is of the same type. If neither of those cases is true, then there is little sense in continuing on with the comparison. Otherwise, we cast the object to an Account type and proceed to test equality on the balance instance field. The cast is necessary, as the default object does not have a balance field.

      public override bool Equals(object obj)      {        if ((obj == null) || (GetType() != obj.GetType()))          return false;        Account acct=(Account)obj;        return acct.balance == balance;      }    }    class AtTheBank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount = new Account(500);        Account mySavings = new Account(500);        Console.WriteLine(myAccount.Equals(mySavings));      }    } 

When this code is executed, it will return that the two objects are equal. This is because we have provided our own Equals() implementation. Our implementation actually tests for value equality. As a side note, we need to point out that our comparison is a bit weak. After all, we would probably never try to determine if two accounts were the same value. However, it should illustrate how to easy it is to set up your own equality tests.

When you compile the above example, it will work, but you will get a warning about not overriding the GetHashCode() method. Let's explore the use of GetHashCode() next.

GetHashCode()

GetHashCode() returns a hash code. This function is used in hashing algorithms and certain data structures, like the hash table. The .NET Framework includes the Hashtable as a fundamental collection object available to developers. Hashtables store objects according to an object's integer hash code. The Hashtable class uses the hash code to sort the objects internally in order to provide faster sorting and searching.

Storage by hash code allows the Hashtable to find a particular object or set of objects within a larger set with minimal effort. The obvious question is where the hash code comes from. In order to develop a hash code for you class, you must override System.Object.GetHashCode().

The default implementation of System.Object.GetHashCode() can only guarantee that the same hash code can be returned for the same instance every time. It cannot guarantee that different instances will have different hash codes. It also cannot guarantee that different instances with the same value will have the same hash code. Overriding System.Object.GetHashCode() gives the programmer the opportunity to return a consistent hash code dependent on the object's state.

There are a couple of rules that should be followed when overriding the GetHashCode() method:

  • If two objects are of the same type and have the same value, they must return the same hash code.

  • The hash code should be based on immutable members. An immutable member's value cannot change once it is created. Imagine getting a hash for an object, storing the object, and then changing a value that the hash relies on. It would likely become impossible to retrieve the object as the hash, used for lookup, has changed.

  • Ensure that hash values are fairly randomly distributed. Well-distributed hash values lend to better search performance.

  • Hash codes should be inexpensive to compute. Simply, it shouldn't take seconds to compute an instance's hash code.

If you are not overriding the GetHashCode() method, do not rely on the hash code for persisting objects. Different versions of .NET might generate different hash codes for the same instance. As such, you cannot guarantee that a hash used in conjunction with persisted objects, say as a primary key when saving state to a database, will always remain valid.

Let us implement a simple GetHashCode() for our Account class, this can be found in sysobject_gethashcode_bankaccount.cs:

    using System;    public class Account    {      private double balance;      private int pin;      public Account(double startingBalance, int PIN)      {        balance=startingBalance;        pin=PIN;      }      public override int GetHashCode()      {        return (pin ^ Convert.ToInt32(balance));      }    }    class AtTheBank    {      [STAThread]      static void Main(string[] args)      {        Account myAccount = new Account(500,1234);        Console.WriteLine(myAccount.GetHashCode());        Console.ReadLine();      }    } 

As a warning, our above implementation is nowhere near being a sound hash code implementation. While, in real life, an account's PIN is immutable (or at least immutable enough), there is an obvious problem in that, at least at a large bank; many accounts could have the same PIN value.

Equals() and GetHashCode()

It is important to implement both GetHashCode() and Equals() together. The Hashtable class uses the GetHashCode() to determine how to sort data, and uses Equals() to verify the objects are indeed equal. If the Equals() method is omitted, the Hashtable cannot verify if two objects are indeed equal. If GetHashCode() is omitted, the Hashtable cannot sort efficiently. The Hashtable class uses both Equals() and GetHashCode() in its design. Omitting one would return unpredictable results.

Regardless of if you use the Hashtable class in your application, overriding Equals() and GetHashCode() is good programming practice and makes the class interface more intuitive and predictable for developers using the class. Many algorithms implement sorting and comparison. Implementing GetHashCode() and Equals() allows your classes to participate in many of .NET's pre-built objects (like Hashtable) and enables developers writing the sorting and searching algorithms to generalize their procedures for objects taking advantage of these members.




C# Class Design Handbook(c) Coding Effective Classes
C# Class Design Handbook: Coding Effective Classes
ISBN: 1590592573
EAN: 2147483647
Year: N/A
Pages: 90

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