David Hayden [MVP C#]

Sponsors

The Lounge

News

  • CodeBetter.Com Home

Other Links

Teas

Patterns & Practices

Florida .NET Developer

Book Reviews

Tampa ASP.NET MVC Developer Group

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Overriding System.Object.ToString() and Implementing IFormattable

A couple of days ago I discussed overriding System.Object.Equals(Object obj) in a post called Object Identity vs. Object Equality - Overriding System.Object.Equals(Object obj).  The gist of that post is essentially that when you create new classes that inherit from System.Object, you may want to consider overriding System.Object's virtual methods so that they make sense for your class.  In that article we had overriden Equals and GetHashCode for the Customer class to suit our needs for equality.

In continuing this idea, System.Object also has a virtual ToString() method that we inherit in our classes.  I find this an absolute must to override, because the method provided by System.Object only returns the name of the class.  Hence the output of the following code will just be "Person."

Not Overriding ToString()

using System;

public class Person
{
    #region Private Members

    private string _firstname;
    private string _lastname;
    private int _age;

    #endregion

    #region Properties

    public string Firstname
    {
        get { return _firstname; }
        set { _firstname = value; }
    }

    public string Lastname
    {
        get { return _lastname; }
        set { _lastname = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }

    #endregion

    #region Contructors

    public Person (string firstname, string lastname, int age)
    {
        if (firstname == null || firstname.Length == 0)
            throw new ArgumentNullException("firstname");

        if (lastname == null || lastname.Length == 0)
            throw new ArgumentNullException("lastname");

        _firstname = firstname;
        _lastname = lastname;
        _age = age;
    }

    #endregion
}

public class TestClass
{
    public static void Main()
    {
        Person p = new Person("David","Hayden",99);
        Console.WriteLine(p); // Outputs "Person"
        Console.ReadLine();
    }
}

That is pretty useless for debugging or anythine else.  By simply overriding ToString(), we can get something a bit more useful.  When we run the new version of our code shown below, we get the following output: "David Hayden, Age = 99".

Overriding ToString()
using System;

public class Person
{
    #region Private Members

    private string _firstname;
    private string _lastname;
    private int _age;

    #endregion

    #region Properties

    public string Firstname
    {
        get { return _firstname; }
        set { _firstname = value; }
    }

    public string Lastname
    {
        get { return _lastname; }
        set { _lastname = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }

    #endregion

    #region Contructors

    public Person (string firstname, string lastname, int age)
    {
        if (firstname == null || firstname.Length == 0)
            throw new ArgumentNullException("firstname");

        if (lastname == null || lastname.Length == 0)
            throw new ArgumentNullException("lastname");

        _firstname = firstname;
        _lastname = lastname;
        _age = age;
    }

    #endregion
    
    public override string ToString()
    {
        return String.Format("{0} {1}, Age = {2}", _firstname,
                               _lastname, _age.ToString());
    }
}

public class TestClass
{
    public static void Main()
    {
        Person p = new Person("David","Hayden",99);
        Console.WriteLine(p); // Outputs "David Hayden, Age = 99"
        Console.ReadLine();
    }
}

So now we are cooking.  However, in the real world, we need way more flexibility in how we want to display this person.  This is where the interface IFormattable comes in.  IFormattable is as basic as you can get.  It has one method:

IFormattable

public string ToString(string format, IFormatProvider formatProvider);

By having our person class implement IFormattable, we can display this person class in a multitude of formats:

Implementing IFormattable
using System;

public class Person : IFormattable
{
    #region Private Members

    private string _firstname;
    private string _lastname;
    private int _age;

    #endregion

    #region Properties

    public string Firstname
    {
        get { return _firstname; }
        set { _firstname = value; }
    }

    public string Lastname
    {
        get { return _lastname; }
        set { _lastname = value; }
    }

    public int Age
    {
        get { return _age; }
        set { _age = value; }
    }

    #endregion

    #region Contructors

    public Person (string firstname, string lastname, int age)
    {
        if (firstname == null || firstname.Length == 0)
            throw new ArgumentNullException("firstname");

        if (lastname == null || lastname.Length == 0)
            throw new ArgumentNullException("lastname");

        _firstname = firstname;
        _lastname = lastname;
        _age = age;
    }

    #endregion

    #region IFormattable Members

    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (format == null) format = "G";

        if (formatProvider != null)
        {
            ICustomFormatter formatter = formatProvider.GetFormat(
                this.GetType())
                as ICustomFormatter;

            if (formatter != null)
                return formatter.Format(format, this, formatProvider);
        }

        switch(format)
        {
            case "f" : return _firstname;
            case "l" : return _lastname;
            case "na" : return string.Format("{0} {1} Age= {2}",
                        _firstname, _lastname, _age.ToString());
            case "n" :
            case "G" :
            default : return string.Format("{0} {1}",
                              _firstname, _lastname);
        }
    }

    #endregion

    public override string ToString()
    {
        return ToString("G", null);
    }

    public string ToString(string format)
    {
        return ToString(format, null);
    }

    public string ToString(IFormatProvider formatProvider)
    {
        return ToString(null, formatProvider);
    }
}

public class TestClass
{
    public static void Main()
    {
        Person p = new Person("David","Hayden",99);
        Console.WriteLine("{0}", p);    // Outputs "David Hayden"
        Console.WriteLine("{0:f}", p);  // Outputs "David"
        Console.WriteLine("{0:l}", p);  // Outputs "Hayden"
        Console.WriteLine("{0:n}", p);  // Outputs "David Hayden"
        Console.WriteLine("{0:na}", p); // Outputs "David Hayden, Age=99"
        Console.ReadLine();
    }
}

The code is fairly straight forward.  Let's forget about formatProvider for this post and just focus on the switch(format) statement.  The statment essentially just returns a different result based on the value of format.  Format "G" is used by the .NET framework, so just include it for completeness.


Posted 02-24-2005 2:23 PM by David Hayden
Filed under:

[Advertisement]

Comments

Brendan Tompkins wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 02-24-2005 4:41 PM
This is cool David! Nice explanation.

I use format strings all the time and love them... I would do the extra work of overriding ToString, if I could use them along with a custom format string. I guess I could just try formatting with the custom string if I don't get one of the expected values, or if I find curly braces. Would that be how you would do it?

Also, I wonder if there would be a clever way, using attributes or something, where you could be more specific about what values you accept in your ToString. I've always hated this... I mean, "G", we use it all the time, but what's that all about? How about something more strongly typed?

Not being nitpicky, but in case you missed this, you don't have to ToString() any of your non-string obejcts passed to String.Format, so this works just as well, and less typing:

string.Format("{0} {1} Age= {2}",_firstname,_lastname,_age);
David Hayden wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 02-24-2005 6:04 PM
Hey Brendan,

In the example above I overloaded ToString a couple of times for flexibility. You can use the overloaded method ToString(string format) to format the person using a given format string.

You could use attributes to help generate documentation. I typically add this type of information in comments so NDoc can add it to the documentation. If you want something during runtime, a possible solution would be to have the "default" in the switch statement as well as the override of ToString() output a list of possible format strings as well as a description of each. You could certainly just reflect on the attributes at runtime.

String.Format is expecting an Object and _age is a value type. If I just pass in "_age", I have to deal with issues of boxing. Better to pass "_age.ToString()" beforehand and you save the performance hit.
Brendan Tompkins wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 02-25-2005 7:39 AM
I do way too much skimming when I read blogs *blush*

"If I just pass in "_age", I have to deal with issues of boxing."

I didn't know this. I need to learn more about boxing and it's preformance issues. Perhaps a post on boxing would be a good one for a CodeBetter blogger to tackle..

Raymond Lewallen wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 02-25-2005 10:40 AM
Brendan, this is why in my post about MSIL, I mentioned that when you understand what your code is doing in MSIL, you write better code :) If you look at the IL, you'll see the boxing instruction for the _age without the ToString(). I am actually working on a post on boxing and unboxing to go up late next week after the final OOP polymorphism post.
TrackBack wrote Boxing and Unboxing - A Quick Visit
on 02-25-2005 12:17 PM
TrackBack wrote Boxing and Unboxing - A Quick Visit
on 02-25-2005 12:30 PM
TrackBack wrote Boxing - The Conversion of Value Types to Reference Types - A Quick Visit
on 02-25-2005 12:43 PM
TrackBack wrote Implementing IComparable for Sorting Custom Objects
on 02-27-2005 9:18 AM
TrackBack wrote Implementing IComparable for Sorting Custom Objects
on 02-27-2005 9:22 AM
TrackBack wrote Implementing IComparable for Sorting Custom Objects
on 02-27-2005 9:29 AM
.Net Adventures wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 03-01-2005 3:04 PM
Check out this article "FormattableObject an Aggregating reflection-based ToString() implementation" by Scott Hanselman
: http://www.hanselman.com/blog/PermaLink,guid,2a0fdc2c-6b15-4a46-a802-0ebc0b8662d9.aspx
TrackBack wrote Implementing IComparable for Sorting Custom Objects
on 03-06-2005 6:08 PM
TrackBack wrote Boxing - The Conversion of Value Types to Reference Types - A Quick Visit
on 03-09-2005 7:41 PM
TrackBack wrote Boxing - The Conversion of Value Types to Reference Types - A Quick Visit
on 03-09-2005 7:55 PM
Jeremy Griffith wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 03-17-2005 5:45 PM
David, thanks for your explanation concerning implementing IFormattable. I have a question.

Can you explain the following aspect of your code....

ICustomFormatter formatter = formatProvider.GetFormat(
this.GetType())
as ICustomFormatter;

this.GetType() would always return a type of Person

your Person type does not implement ICustomFormatter therefore the formatter variable is always going to equal null...

so what is the point of what follows next...

if (formatter != null)
return formatter.Format(format, this, formatProvider);

the above line of code would never execute...

am I misunderstanding something?

David Hayden wrote re: Overriding System.Object.ToString() and Implementing IFormattable
on 03-17-2005 6:16 PM
You're absolutely right, Jeremy. In this case, the code would never execute.

However, for some of your classes, you may implement ICustomFormatter, which is why I always have the check in there. Basically I am just future proofing that method so that if later on I do implement ICustomFormatter it will work as expected and pass control to the ICustomFormatter interface.

Great question!
TrackBack wrote Create a Shopping Cart - Inspired by ASP.NET Daily Articles
on 03-21-2005 7:15 PM
TrackBack wrote Create a Shopping Cart - Inspired by ASP.NET Daily Articles
on 03-21-2005 7:19 PM
David Hayden wrote Create a Shopping Cart - Inspired by ASP.NET Daily Articles
on 03-22-2005 4:53 PM
A while ago I noticed that one of the ASP.NET Daily Articles was an article on how to create a shopping...
Relax, Sip & Enjoy Dev Blog wrote Create a Shopping Cart - Inspired by ASP.NET Daily Articles
on 03-25-2005 6:37 AM
Relax, Sip & Enjoy Dev Blog wrote Create a Shopping Cart - Inspired by ASP.NET Daily Articles
on 03-25-2005 6:37 AM