Hanalei, Hawaii Tuesday, February 09, 2010

Tip: Overriding Core Object Methods

Every object in C# inherits 3 overridable core methods, all stemming from "Object": ToString(): represents an object if you convert it to a String. By default, this is the object Type.

Every object in C# inherits 3 overridable core methods, all stemming from "Object":

ToString(): represents an object if you convert it to a String. By default, this is the object Type.

Equals(): this method return true/false to indicate it's equality with another object of the same type.

GetHashCode(): a more obscure method, but (basically) this method creates a hash key that identifies an object in a hashing algorithm. In other words, if you pop your object into a HashTable, GetHashCode() calculates a key for that object.

In this post I'll discuss how overriding these methods can help you test and debug your application.

 

Each core object method (ToString(), Equals(), and GetHashCode()) is overridable - meaning you can redefine them on the object that you create. Most of the time people don't do this since it's not readily apparent as to why you should. But I invite you to take a second and rethink this (if you don't override them already) as it will help you tremendously when testing and debugging.

 

ToString(): Your Little Testing Buddy
I'm currently working on some very fun stuff with SubSonic (more on that later) and I've got a whole mess of tests in front of me that I sometimes run with the debugger so I can step through the code to see what's going on and just how badly I'm screwing things up. For primitive variables, it's pretty easy to take a look at the Locals window (or hover over them) and see their value:

immediate1

In this case, I can see clearly that records=0, which is what it should be in this execution step.

But that's not so easy when it comes to the next step - the query object that I'm trying to create. It's an object, and when I hover over it all I get is "SubSonic.SqlQuery" - which isn't really helpful. This is because, by default, the Locals window (or hovering over the instance) will return GetType() as the default ToString() value. This can be helpful sometimes, but mostly I already know the type I'm working with.

 

Easy Overrider
If you override ToString() however, it's current state can become a lot more clearer. Determining this "state" is relative to each object - in the case of my query object, it might make sense to output the SQL statement. If you have a Person or Contact, outputting the FirstName and LastName would make more sense. This part's up to you - but whatever you choose, make sure it will make sense to you and your team (and more importantly those who will pick up your code after you).

In the case of my SqlQuery object above, I overrode the ToString() method to return the current underlying SQL since, to me, this is what a query "is" (although it depends what your definition of "is" is):

override

So now when I'm testing, I can see what's returned and moreover, have a little more detail as to why the test fails:

test2

In this test I've output the SQL to a string using ToString(), but you can also see how, in the Locals window below, "q" (which is my SqlQuery instance) is resolved to the SQL string, which is really helpful.

I'm currently overriding ToString() for many of the core objects in SubSonic to help with testing - such as returning the qualified name for a TableSchema and TableColumn:

override2

 

Using Equals() and GetHashCode()
This is not as straightforward as just outputting string values to represent an object's state or value. How do you determine if one object equals another?

Let's use TableSchema again (which, with SubSonic, describes the database settings for a given table). For one object to be equal to another, we can say that:

  1. The table name should be the same
  2. The column names should be the same

One could argue that we should have more here - especially if the schema is changeable at runtime (which it is). But let's pretend it's not so I can cut down on the length of this post :). Here's how you might implement this test for Equals():

equals

It doesn't make too much sense to override GetHashCode() for a column or table schema (since it needs an Int32 value), but it does for each ActiveRecord instance (using SubSonic) - we can return the Primary Key value so that any Hash you use with a Product object (for instance) is keyed using the Product instance Primary Key value:

product

You can also see here how I've overridden ToString() to return a "descriptor" column - a column that uniquely describes the instance in human terms. For a Product - this is the ProductName.

By convention we've always used the second ordinal column in a table as a "descriptor" - but I've loosened up on this and made it a touch more formal, so now you can access a "Descriptor" property which returns the first string column after the primary key. This will be available in the next release - no I have no date for it :).

So now, when you use ToString() on a Product, you'll get back "Chai" for instance if you're using Northwind.

 

Summary
I'll write more on the changes and updates to SubSonic in another post (coming this week) - for now I thought it would be fun to write a post on how "test and debug friendly" you can make your objects by overriding the core object methods.


Elmar - January 6, 2008 - All I see is beautiful query language ;-))
Jason - January 6, 2008 - Instead of overriding the ToString() method, I usually just slap a System.Diagnostics.DebuggerDisplayAttribute on my class. It gives you the benefit of being able to display multiple properties in the debugger as well. For example, if I had a class that represented a customer that had a FirstName and a LastName property, I could make it appear in the debugger as {LastName}, {FirstName}. http://msdn2.microsoft.com/en-us/library/system.diagnostics.debuggerdisplayattribute.aspx
Aaron Lerch - January 6, 2008 - Good stuff! IMHO, though, I'd rather use the DebuggerDisplay attribute on a class/property to control the debugger's view. That way you're not altering program function for something only used in debugging, not real world execution. http://msdn2.microsoft.com/en-us/library/system.diagnostics.debuggerdisplayattribute.aspx
Aaron Lerch - January 6, 2008 - Dang it, Jason beat me to the punch by mere seconds! :)
Glenn - January 6, 2008 - I like the DebuggerDisplay attribute, but I like to give to ToString() method a meaningful value too, as it helps with things like console debugging or file logging. If I have an object & I want to keep a log I can just write it out rather than having to add display logic into my app. I like to think of ToString() as the label for my objects
mike - January 6, 2008 - How about a debugger visualizer? A quick search on Google: http://msdn2.microsoft.com/en-us/library/zayyhzts.aspx
Steven Harman - January 6, 2008 - When overriding the Equals method I usually make sure to also override the GetHashCode so as to maintain the general contract that equal objects also have an equal hashcode. Actually, back in my Java days I wrote explicitly about how and why to override Equals and HashCode.
Cory - January 7, 2008 - Would you mind posting your vssettings file?
Gecko