System.Object 2020
System.Object (object) is the ultimate base class for all types.
Like any class, System.Object defines a set of members. Some of these are declared virtual and therefore they may be overridden by a subclass. Others are marked with static, so they can be called at the class level.
public class Object { // virtual members public virtual bool Equals(object obj); protected virtual void Finalize(); public virtual int GetHashCode(); public virtual string ToString(); // Instance level, nonvirtual members public Type GetType(); protected object MemberwiseClone(); // Static members putbli static bool Equals(object obj1, object obj2); public static bool ReferenceEquals(object obj1, object obj2); }
TABLE
Equals() | By default, this method returns true only if the items being compared refer to the exact same item in memory. So, Equals() is used to compare object references, not the state of the object. Actually, this method is overridden to return true only if the objects being compared have the same internal state values (that is, value-based semantics. Be aware that if we override Equals(), we should also override GetHashCode(), as these methods are used internally bay Hashtable types to retrieve subobject from the container. |
GetHashCode() | This method returns an int that identifies a specific object instance. |
GetType() | This method returns a Type object that fully describes the object we are referencing. In other words, this is a Runtime Type Identification (RTTI) method available to all objects. |
ToString() | This method returns a string representation of the object, using the <namespace;>.<type name> format. This method can be overridden by a subclass to return a tokenized string of name/value pairs that represent the object's internal state rather than its fully qualified name. |
Finalize() | This method is called to free any allocated resources before the object is destroyed. |
MemberwiseClone() | This method exists to return a member-by-member copy of the current object, which is often used when cloning an object. |
Any type can be upcast to object. To illustrate how this is useful, consider a general-purpose stack. A tack is a data structure based on LIFO (Last-In-First-Out). A stack has two operations: push an object on the stack, and pop an object off the stack. Here is a simple implementation that can hold up to 5 objects:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SystemObject { class Stack { int position; object[] data = new object[5]; public void Push(object obj) { data[position++] = obj; } public object Pop() { return data[--position]; } } class Program { static void Main(string[] args) { Stack st = new Stack(); st.Push("Stephenie Meyer"); // This is downcase, so we need explicit cast string s = (string) st.Pop(); Console.WriteLine(s); } } }
The output is:
Stephenie Meyer
Because Stack works with the object type, we can Push and Pop instances of any type to and from the Stack.
object is a reference type. Value types such as int can also be cast to and from object. as so be added to our stack. This feature of C# is called type unification and is demonstrated here:
stack.Push(3); int three = (int) stack.Pop();
When we cast between a value type and object, the CLR must perform some special work to bridge the difference in semantics between value and reference types. This process is called boxing and unboxing.
To illustrate some of the default behavior provided by the Object base class, let's look at more directly related to the object with simplified version of Stack class.
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SystemObject { class Stack {} class Program { static void Main(string[] args) { Stack stk1 = new Stack(); Console.WriteLine("ToString: {0}", stk1.ToString()); Console.WriteLine("Hash TypeCode: {0}", stk1.GetHashCode()); Console.WriteLine("Type: {0}", stk1.GetType()); Stack stk2 = stk1; object o = stk2; if (o.Equals(stk1) && stk2.Equals(o)) { Console.WriteLine("Same Instance!"); } Console.ReadLine(); } } }
We'll get the following output:
ToString: SystemObject.Stack Hash TypeCode: 46104728 Type: SystemObject.Stack Same Instance!
Note that the default implementation of ToString() returns the fully qualified name of the current type (SystemObject.Stack). The default behavior of Equals() is to test whether two variables are pointing to the same object in memory. Here, we create a new Stack variable named stk1. At this point, a new Stack object is placed on the managed heap. stk2 is also of the Stack. However, we are not creating a new instance, but rather assigning this variable to reference stk1. So, stk1 and stk2 are both pointing to the same object in memory, as is the variable o. Therefore, the equality test succeeds.
Although the canned behavior of System.Object can fit the bill in a number of cases, it is quite common for us custom types to override some of these inherited methods. To demonstrate, let's make a new Student class to support some state data representing each student's first name, last name, and id.
using System; namespace SystemObject { class Student { public string firstName; public string lastName; public byte id; public Student(string fn, string ln, byte i) { firstName = fn; lastName = ln; id = i; } public Student() { } } class Program { static void Main(string[] args) { Console.ReadLine(); } } }
Many classes can benefit from overriding ToString in order to return a string representation of the type's state. The recommended way of constructing this string is to separate each name/value pair with semicolons and wrap the entire string within square brackets:
public override string ToString() { string myState; myState = string.Format( "[First Name: {0}; Last Name: {1}; ID : {2}]", firstName, lastName, id); return myState; }
This implementation of ToString() is straightforward, given that the Student class has three pieces of state data.
To work with value-based semantics, let's also override the behavior of Object.Equals(). Equals(), by default, returns true only if the two objects being compared reference the same object instance in memory. For the Student class, it may be helpful to implement Equals to return true if the two variables being compared contain the same state values (firstName, lastName, and id).
First of all, note that the incoming argument of the Equals() method is a generic System.Object. Given this, we should make it sure that the called has indeed passed in a Student type. We should also make sure that the incoming parameter is not an unallocated object.
Once we have established the caller has passed us an allocated Student, one approach to implement Equals() is to perform a field-by-field comparison against the data of the incoming object to the data of the current object:
public override bool Equals(object obj) { if (obj is Student && obj != null) { Student temp; temp = (Student)obj; if (temp.firstName == this.firstName && temp.lastName == this.lastName && temp.id == this.id) { return true; } else { return false; } } return false; }
When a class overrides the Equals() method, we should also override the default implementation of GetHashCode(). A hash code is a numerical value that represents an object as a particular state. For instance, if we create two string objects that hold the value Hello, we would obtain the same has code. However, if one of the string objects were in all lowercase(hello), we would obtain different hash codes.
By default, System.Object.GetHashCode() uses our object's current location in memory to yield the hash value. But if we are building a custom type that we intend to store in a Hashtable type, we should always override this member, as the Hashtable will be internally invoking Equals() and GetHashCode() to retrieve the correct object.
Most of the time, we can generate a hash code value by using the System.String's GetHashCode() implementation.
Since the String class already has a hash code algorithm that is using the character data of the String to compute a hash value, if we can identify field data on our class that should be unique for all instances, we can simply call GetHashCode() on that string data:
public override int GetHashCode() { return this.ToString().GetHashCode(); }
Here is a test code which combines the overridden methods together:
using System; namespace SystemObject { class Student { public string firstName; public string lastName; public byte id; public Student(string fn, string ln, byte i) { firstName = fn; lastName = ln; id = i; } public Student() { } public override string ToString() { string myState; myState = string.Format( "[First Name: {0}; Last Name: {1}; ID : {2}]", firstName, lastName, id); return myState; } public override bool Equals(object obj) { if (obj is Student && obj != null) { Student temp; temp = (Student)obj; if (temp.firstName == this.firstName && temp.lastName == this.lastName && temp.id == this.id) { return true; } else { return false; } } return false; } // return a hash code based on the student's ToString() value public override int GetHashCode() { return this.ToString().GetHashCode(); } } class Program { static void Main(string[] args) { Student s1 = new Student("Francis", "Fitzgerald", 101); Student s2 = new Student("Francis", "Fitzgerald", 101); Console.WriteLine("s1.ToString() = {0}", s1.ToString()); Console.WriteLine("s2.ToString() = {0}", s2.ToString()); Console.WriteLine("s1 == s2 ? : {0}", s1.Equals(s2)); Console.WriteLine("HashCode same? : {0}", s1.GetHashCode() == s2.GetHashCode()); Console.WriteLine(); s2.id = 111; Console.WriteLine("s2's id has been changed"); Console.WriteLine("s1.ToString() = {0}", s1.ToString()); Console.WriteLine("s2.ToString() = {0}", s2.ToString()); Console.WriteLine("s1 == s2 ? : {0}", s1.Equals(s2)); Console.WriteLine("HashCode same? : {0}", s1.GetHashCode() == s2.GetHashCode()); Console.ReadLine(); } } }
If we run the code, we get:
s1.ToString() = [First Name: Francis; Last Name: Fitzgerald; ID : 101] s2.ToString() = [First Name: Francis; Last Name: Fitzgerald; ID : 101] s1 == s2 ? : True HashCode same? : True s2's id has been changed s1.ToString() = [First Name: Francis; Last Name: Fitzgerald; ID : 101] s2.ToString() = [First Name: Francis; Last Name: Fitzgerald; ID : 111] s1 == s2 ? : False
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization