FREE BOOK

Chapter 2: Creating Versatile Types

Posted by SAMS Publishing Free Book | C# Language March 24, 2010
Tags: C#
This chapter is all about making your own objects as useful and versatile as possible. In many cases, this means implementing the standard interfaces that .NET provides or simply overriding base class methods.

Implement Custom Formatting for Fine Control

Scenario/Problem: You need to provide consumers of your class fine-grained control over how string representations of your class look.

Solution: Although the ToString()implementation gets the job done, and is especially handy for debugging (Visual Studio will automatically call ToString() on objects in the debugger windows), it is not very flexible. By implementing IFormattable on your type, you can create a version of ToString() that is as ­flexible as you need.

Let's create a simple format syntax that allows us to specify which of the three values to print. To do this, we'll define the following format string:

"X, Y"

This tells Vertex3d to print out X and Y. The comma and space (and any other character) will be output as-is.

The struct definition will now be as follows:

using System;
using
System.Collections.Generic;
using
System.Text; 

namespace VertexDemo
{
    struct Vertex3d : IFormattable
    {
        ...
        public string ToString(string format, IFormatProvider formatProvider)
        {
            //"G" is .Net's standard for general formatting--all
            //types should support it
            if (format == null) format = "G";
            // is the user providing their own format provider?
            if (formatProvider != null)
            {
                ICustomFormatter formatter = formatProvider.GetFormat(this.GetType()) as ICustomFormatter;
                if (formatter != null)
                {
                    return formatter.Format(format, this, formatProvider);
                }
            }
            //formatting is up to us, so let's do it
            if (format == "G")
            {
                return string.Format("({0}, {1}, {2})", X, Y, Z);
            }
            StringBuilder sb = new StringBuilder();
            int sourceIndex = 0;
            while (sourceIndex < format.Length)
            {
                switch (format[sourceIndex])
                {
                    case 'X':
                        sb.Append(X.ToString());
                        break;
                    case 'Y':
                        sb.Append(Y.ToString());
                        break;
                    case 'Z':
                        sb.Append(Z.ToString());
                        break;
                    default:
                        sb.Append(format[sourceIndex]);
                        break;
                }
                sourceIndex++;
            }
            return sb.ToString();
        }
    }

}

The formatProvider argument allows you to pass in a formatter that does something different from the type's own formatting (say, if you can't change the implementation of ToString() on Vertex3d for some reason, or you need to apply different formatting in specific situations). You'll see how to define a custom formatter in the next section.

Formatting with ICustomFormatter and StringBuilder

Scenario/Problem: You need a general-purpose formatter than can apply custom formats to many types of objects.

Solution: Use ICustomFormatter and StringBuilder. This example prints out type information, as well as whatever the custom format string specifies for the given types.

class TypeFormatter : IFormatProvider, ICustomFormatter
{
    public object GetFormat(Type formatType)
    {
        if (formatType == typeof(ICustomFormatter)) return this;
        return Thread.CurrentThread.CurrentCulture.GetFormat(formatType);
    } 

    public string Format(string format, object arg, IFormatProvider formatProvider)
    {
        string value;
        IFormattable formattable = arg as IFormattable;
        if (formattable == null)
        {
            value = arg.ToString();
        }
        else
        {
            value = formattable.ToString(format, formatProvider);
        }
        return string.Format("Type: {0}, Value: {1}", arg.GetType(), value);
    }

}

The class can be used like this:

Vertex3d v = new Vertex3d(1.0, 2.0, 3.0);
Vertex3d
v2 = new Vertex3d(4.0, 5.0, 6.0);
TypeFormatter
formatter = new TypeFormatter();
StringBuilder sb = new StringBuilder();
sb.AppendFormat(formatter, "{0:(X Y)}; {1:[X, Y, Z]}", v, v2);

Console
.WriteLine(sb.ToString());

The following output is produced:

Type: ch02.Vertex3d, Value: (1 2); Type: ch02.Vertex3d, Value: [4, 5, 6]

Total Pages : 12 12345

comments