for RuBoard |
As we have already seen, every type in C#, whether it is a value type or a reference type, ultimately inherits from the root class System.Object . C# provides object as a keyword alias for this root class. The class ValueType inherits directly from object. ValueType is the root for all value types, such as structures and simple types like int and decimal .
There are four public instance methods of object , three of which are virtual and frequently overridden by classes.
public virtual bool Equals(object obj);
This method compares an object with the object passed as a parameter and returns true if they are equal. object implements this method to test for reference equality. ValueType overrides the method to test for content equality. Many classes override the method to make equality behave appropriately for the particular class.
public virtual string ToString();
This method returns a human-readable string representation of the object. The default implementation returns the type name . Derived classes frequently override this method to return a meaningful string representation of the particular object.
public virtual int GetHashCode();
This method returns a hash value for an object, suitable for use in hashing algorithms and hash tables. You should normally override this method if you override ToString . (The C# compiler will give you a warning message if you override one and not the other.)
public Type GetType();
This method returns type information for the object. This type information can be used to get the associated metadata through reflection , a topic we discuss in Chapter 8.
There are two protected instance methods, which can be used only within derived classes.
protected object MemberwiseClone();
This method creates a shallow copy of the object. To perform a deep copy, you should implement the ICloneable interface We will discuss shallow and deep copy later in this chapter.
~Object();
This method allows an object to free resources and perform other cleanup operations before it is reclaimed by garbage collection. In C# the Finalize method is represented by "destructor" notation like that used in C++. But note that the semantics are totally different. In C++, destructors are invoked in a deterministic manner, which the programmer can depend upon. In C#, finalization is nondeterministic, dependent upon the garbage collector. We discuss finalization in Chapter 8.
If you are used to a language like Smalltalk, the set of behaviors specified in object may seem quite limited. Smalltalk, which introduced the concept of a class hierarchy rooted in a common base class, has a very rich set of methods defined in its Object class. I counted 38 methods! [1] These additional methods support features such as comparing objects and copying objects. The .NET Framework class library has similar methods, and many more. But rather than putting them all in a common root class, .NET defines a number of standard interfaces , which classes can optionally support. This kind of organization, which is also present in Microsoft's Component Object Model (COM) and in Java, is very flexible. We will study interfaces later in this chapter, and we will discuss some of the generic interfaces of the .NET Framework.
[1] The methods of Smalltalk's Object class are described in Chapters 6 and 14 of Smalltalk-80: The Language and its Implementation, by Adele Goldberg and David Robson.
As a simple illustration of object methods, let's look at our Customer class before and after overriding the Equals , ToString , and GetHashCode methods.
If our class does not provide any overrides of the virtual instance methods of object , our class will inherit the standard behavior. This behavior is demonstrated in CustomerObject\Step1 .
// Customer.cs public class Customer { public int CustomerId; public string FirstName; public string LastName; public string EmailAddress; public Customer(int id, string first, string last, string email) { CustomerId = id; FirstName = first; LastName = last; EmailAddress = email; } }
Here is the test program:
// TestCustomer.cs using System; public class TestCustomer { public static void Main() { Customer cust1, cust2; cust1 = new Customer(99, "John", "Doe", "john@rocky.com"); cust2 = new Customer(99, "John", "Doe", "john@rocky.com"); ShowCustomerObject("cust1", cust1); ShowCustomerObject("cust2", cust2); CompareCustomerObjects(cust1, cust2); } private static void ShowCustomerObject(string label, Customer cust) { Console.WriteLine("---- {0} ----", label); Console.WriteLine("ToString() = {0}", cust.ToString()); Console.WriteLine("GetHashCode() = {0}", cust.GetHashCode()); Console.WriteLine("GetType() = {0}", cust.GetType()); } private static void CompareCustomerObjects( Customer cust1, Customer cust2) { Console.WriteLine("Equals() = {0}", cust1.Equals(cust2)); } }
Run the test program and you will see this output:
---- cust1 ---- ToString() = Customer GetHashCode() = 4 GetType() = Customer ---- cust2 ---- ToString() = Customer GetHashCode() = 6 GetType() = Customer Equals() = False
The default implementation is not at all what we want for our Customer object. ToString returns the name of the class, not information about a particular customer. Equals checks for reference equality. In our example, we have two different references to Customer objects with the same content, and Equals return false .
The version of the project in CustomerObject\Step2 demonstrates overriding these virtual methods. Our override of Equals tests for content equality.
// Customer.cs public class Customer { public int CustomerId; public string FirstName; public string LastName; public string EmailAddress; public Customer(int id, string first, string last, string email) { CustomerId = id; FirstName = first; LastName = last; EmailAddress = email; } public override bool Equals(object obj) { Customer cust = (Customer) obj; return (cust.CustomerId == CustomerId); } public override int GetHashCode() { return CustomerId; } public override string ToString() { return FirstName + " " + LastName ; } }
The test program is identical. Here is the new output:
---- cust1 ---- ToString() = John Doe GetHashCode() = 99 GetType() = Customer ---- cust2 ---- ToString() = John Doe GetHashCode() = 99 GetType() = Customer Equals() = True
for RuBoard |