[C#] System.Object

The “System.Object” class is the ultimate (master) base class of all classes in the .NET Framework, which means:

  • All methods in the “System.Object” class are available in all .NET objects
  • All classes can be casted into the “System.Object” type

When you create a custom class, you do not need to override any methods in the “Object” class. But sometimes the default implementation of the methods by the “Object” class might not be appropriate for your need. Let’s find out what the “Object” class offers to us.                     

– Instance Methods of the “Object” class –

  • public virtual bool Equals(object obj)
  • public virtual int GetHashCode()
  • public virtual string ToString()
  • protected virtual void Finalize()
  • public Type GetType()
  • protected Object MemberwiseClone()

– Static Methods of the “Object” class –

  • public static bool Equals(Object objA, Object objB)
  • public static bool ReferenceEquals(Object objA, Object objB)

As you can see, the “Object” class provides several virtual methods. When you declare your custom classes, you need to focus on virtual methods, for which you might need to provide your own implementation. Also the “Object” class provides helper methods such as GetType() or static methods.

 

1. Provide the text representation of an object

The “ToString()” method is the first one you want to override in your custom classes. It is an instance method, which means that the method should return the description of an instance, not a class in general.

The default implementation returns the fully qualified name of the type, which usually does not fit your needs.

namespace OOP
{
    public class Account
    {
        public string ownerName;
        public decimal balance;
    }
}
Account acc = new Account();
Console.WriteLine(acc.ToString());  // "OOP.Account"

In this case, the default implementation does not provide any instance status. We can override the “ToString()” method in order to return the better description of an instance.

public class Account
{
    public string ownerName;
    public decimal balance;

    public override string ToString()
    {
        return string.Format("Owner: {0}, Current Balance: ${1:0.00}", ownerName, balance);
    }
}
Account acc = new Account();
acc.ownerName = "Harry";
acc.balance = 100;
Console.WriteLine(acc.ToString()); // Owner: Harry, Current Balance: $100.00

 

2. Object Equality

How can you determine whether 2 objects are the same?

C# provides 2 ways to do that.

  • == (equality operator)
  • Equals() method

The “Equals()” method is defined in the “System.Object” class, therefore all objects can use the “Equals()” method.

For value types, the equality operator (==) works as you expected. But for reference types, the equality operator performs the reference equality, which checks 2 references really point to the same object in the memory.

The default implementation of the “Equals()” method is the reference equality too.

public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y) { this.X = x; this.Y = y; }
}
Point p1 = new Point(2,3);
Point p2 = new Point(2,3);

if (p1 == p1) Console.WriteLine("p1==p1"); // true
if (p1 == p2) Console.WriteLine("p1==p2"); // false
if (p1.Equals(p1)) Console.WriteLine("p1.Equals(p1)"); // true
if (p1.Equals(p2)) Console.WriteLine("p1.Equals(p2)"); // false

The (p1==p1) and (p1.Equals(p1)) are true as expected. And p1 and p2 are not equal.

But for the “Point” objects, you might think if both x and y values are the same, two points should be equal even though they are located in the different memory locations. To compare 2 objects using their member values, you need to override the “Equals()” method and compare 2 objects using the “Equals()” method rather than the equality operator(==).

But when you provide your version of the “Equals()” method, it should meet the following requirements:

  • x.Equals(x) returns true
  • x.Equals(y) returns the same value as y.Equals(x)
  • x.Equals(y) returns true if both x and y are NaN.
  • If (x.Equals(y) && y.Equals(z)) returns true, then x.Equals(z) returns true
  • Successive calls to x.Equals(y) return the same value as long as the objects referenced by x and y are not modified
  • x.Equals(null) returns false
  • Implementations of Equals must not throw exceptions.

Let’s override the “Equals()” method in the “Point” class.

public override bool Equals(object obj)
{
    // x.Equals(null) returns false
    // should be the same type

    if ((obj != null) && (obj is Point))
    {
        Point p = obj as Point;
        return (this.X == p.X) && (this.Y == p.Y);
    }
    return false;
}
Point x = new Point(2,3);
Point y = new Point(2,3);
Object z = new Int32();

if (x.Equals(x)) Console.WriteLine("x.Equals(x)"); // true
if (x.Equals(y)) Console.WriteLine("x.Equals(y)"); // true
if (y.Equals(x)) Console.WriteLine("y.Equals(x)"); // true
if (x.Equals(null)) Console.WriteLine("x.Equals(null)"); // false
if (x.Equals(z)) Console.WriteLine("x.Equals(int32)"); // false

 

3. Hashing ???

Have you noticed that the compiler is warning you when you compile the above code?

‘OOP.Point’ overrides Object.Equals(object o) but does not override Object.GetHashCode()

The warning says you do not override the “GetHashCode()“.

What is a hash code? What is this stuff for?

In short, a hash code is a numeric value (integer for .NET Framework) that represents the status of an object and is used to check the equality between objects. If two objects are equal, they should have the same hash code. The opposite is not true, though. The same hash code does not mean they are equal.

Here is the point. When you override the “Equals()” method, you should make sure equal objects should return the same hash code.

The “GetHashCode()” method  has the following properties:

  • If two objects compare as equal, the GetHashCode() method for each object must return the same value
  • The GetHashCode() method for an object must consistently return the same hash code unless the object changes its inner status

The idea to create a hash code might daunting to you. I am not a computer science PhD. How can I make up the logic for all my classes?

Here is the workaround for almost all developers. We are using the hash function of the “System.String” class. This requires two steps:

  • Overrive the “ToString()” method to represent all meaningful status of an instance
  • In the “GetHashCode()” method, return the value of “ToString().GetHashCode()”.
public override string ToString()
{
    return string.Format("X: {0}, Y: {1}", this.X, this.Y);
}
public override int GetHashCode()
{
    return ToString().GetHashCode();
}
Point x = new Point(2, 3);
Point y = new Point(2, 3);
Point z = new Point(3, 4);

Console.WriteLine(x.GetHashCode()); // -1649762150
Console.WriteLine(y.GetHashCode()); // -1649762150
Console.WriteLine(z.GetHashCode()); // -1629970278

Yup! x and y produce the same hash code!!!

One more bonus! If your “ToString()” includes all required status, it is pretty safe to use it in your “Equals()” method. Do not bother with all those checking logics (null, type, values). Instead, just do like this:

public override bool Equals(object obj)
{
    return ((obj != null) && (this.ToString() == obj.ToString()));
}

 

4. Getting the type of an object

The “System.Type” class represents the type in the .NET framework. You can get a lot of information about the type through this class. But how do you get an Type object in your code? There are 3 ways:

  • Without an object instance: Use the “typeof” operator or the “Type” class’s  static method GetType()
  • Using an object instance: Call the “GetType()” method. The “GetType()” method is defined in the “Object” class, so it can be used with any object
  • Use reflections: you can drill down from assemblies to types and even members
Point x = new Point(2, 3);

Console.WriteLine(typeof(Point).FullName);
Console.WriteLine(Type.GetType("OOP.Point").FullName);
Console.WriteLine(x.GetType().FullName);

 

5. Object class’s Static Methods

The Object class provides 2 static methods to check the equality of objects.

  • public static bool Equals(Object objA, Object objB)
  • public static bool ReferenceEquals(Object objA, Object objB)

The “ReferenceEquals()” method is easy. It always checks the reference equality.

Meanwhile, the static “Equals()” requires more explanation. This static method is provided for the value equality. For value types, it is crystal clear. How about reference types? In the static “Equals(Object objA, Object objB)method calls the “objA.Equals()” method to check the equality. If you do not override the “Equals()method, the static “Equals()” does the reference checking.

Point x = new Point(2, 3);
Point y = new Point(2, 3);

bool isEqual = Object.Equals(x, y);

If you do not override the “Equal()” method, the result will be “false”. If you override the “Equals()” method to check the values, the result will be “true”.

 

6. Best Practices

Whenever you create a custom class, it is a good practice to override the following 3 methods even if you do not use them directly. Your coworkers might use your class in the hashcode. Who knows?

  • public virtual bool Equals(object obj)
  • public virtual int GetHashCode()
  • public virtual string ToString()

[Note] I haven’t covered all methods in the “System.Object” class in this article. I think they are not critical for everyday programming. You can refer to MSDN for these methods at any time.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s