Your class or structure needs
to control how its information is displayed when its
ToString
method is called. For example, when
creating a new data type, such as a Line
class,
you might want to allow objects of this type to be able to display
themselves in a textual format. In the case of a
Line
object, it would display itself as
"(x1
, y1)(x2, y2)
“.
Override and/or implement the
IFormattable.ToString
method to display numeric
information, such as for a Line
structure:
using System; using System.Text; using System.Text.RegularExpressions; public struct Line : IFormattable { public Line(int startX, int startY, int endX, int endY) { x1 = startX; x2 = endX; y1 = startY; y2 = endY; } public int x1; public int y1; public int x2; public int y2; public double GetDirectionInRadians( ) { int xSide = x2 - x1; int ySide = y2 - y1; if (xSide == 0) // Prevent divide-by-zero return (0); else return (Math.Atan (ySide / xSide)); } public double GetMagnitude( ) { int xSide = x2 - x1; int ySide = y2 - y1; return (Math.Sqrt( Math.Sqrt((xSide * xSide) + (ySide * ySide)) )); } // This overrides the Object.ToString method // This override is not required for this recipe // and is included for completeness public override string ToString( ) { return (String.Format("({0},{1}) ({2},{3})", x1, y1, x2, y2)); } public string ToString(string format) { return (this.ToString(format, null)); } public string ToString(IFormatProvider formatProvider) { return (this.ToString(null, formatProvider)); } public string ToString(string format, IFormatProvider formatProvider) { StringBuilder compositeStr = new StringBuilder(""); if ((format != null) && (format.ToUpper( ).Equals("V"))) { double direction = this.GetDirectionInRadians( ); double magnitude = this.GetMagnitude( ); string retStringD = direction.ToString("G5", formatProvider); string retStringM = magnitude.ToString("G5", formatProvider); compositeStr.Append("magnitude = ").Append(retStringM).Append (" Direction = ").Append(retStringD); } else { string retStringX1 = this.x1.ToString(format, formatProvider); string retStringY1 = this.y1.ToString(format, formatProvider); string retStringX2 = this.x2.ToString(format, formatProvider); string retStringY2 = this.y2.ToString(format, formatProvider); compositeStr.Append("(").Append(retStringX1).Append(",").Append (retStringY1).Append(")(").Append(retStringX2).Append (",").Append(retStringY2).Append(")"); } return (compositeStr.ToString( )); } }
The ToString
method provides a convenient way to
display the current contents, or state, of a structure (this recipe
works equally well for reference types). The solution section of this
recipe shows the various implementations of
ToString
for both numeric and textual data. The
Line
class contains two points in space that form
the endpoints of a line. This line data is then fed into the
ToString
methods for that class to produce
formatted output.
The following code exercises the ToString
methods
of the Line
class:
using System.Globalization; public void TestLineToString( ) { Line V1 = new Line(0, 0, 40, 123); Line V2 = new Line(0, -2, 1, 11); Line V3 = new Line(0, 1, 0, 1); Console.WriteLine(" Test Default ToString method"); Console.WriteLine("V1 = " + V1); Console.WriteLine("V2 = " + V2); Console.WriteLine("V1.ToString( ) = {0:V}", V1.ToString( )); Console.WriteLine("V2.ToString( ) = {0:V}", V2.ToString( )); Console.WriteLine(" Test overloaded ToString(format) method"); Console.WriteLine("V1.ToString("D") = {0:D}", V1); Console.WriteLine("V1.ToString("D5") = {0:D5}", V1); Console.WriteLine("V2.ToString("F") = {0:F}", V2); Console.WriteLine("V1.ToString("N") = {0:N}", V1); Console.WriteLine("V2.ToString("n") = {0:n}", V2); Console.WriteLine("V1.ToString("E") = {0:E}", V1); Console.WriteLine("V2.ToString("X") = {0:X}", V2); Console.WriteLine(" Test overloaded ToString(formatProvider) method"); NumberFormatInfo NullFormatter = null; NumberFormatInfo Formatter = new NumberFormatInfo( ); Formatter.NegativeSign = "!"; Formatter.PositiveSign = "+"; Console.WriteLine("V2.ToString(Formatter) = " + V2.ToString(Formatter)); Console.WriteLine("V2.ToString(Formatter) = " + V2.ToString(Formatter)); Console.WriteLine("V2.ToString(null) = " + V2.ToString(NullFormatter)); Console.WriteLine("V2.ToString(null) = " + V2.ToString(NullFormatter)); Console.WriteLine("V2.ToString(new CultureInfo("fr-BE")) = " + V2.ToString(new CultureInfo("fr-BE"))); //French - Belgium Console.WriteLine("V2.ToString(new CultureInfo("fr-BE")) = " + V2.ToString(new CultureInfo("fr-BE"))); //French - Belgium Console.WriteLine (" Test overloaded ToString(format, formatProvider) method"); Console.WriteLine("V2.ToString("D", Formatter) = " + V2.ToString("D", Formatter)); Console.WriteLine("V2.ToString("F", Formatter) = " + V2.ToString("F", Formatter)); Console.WriteLine("V2.ToString("D", null) = " + V2.ToString("D", null)); Console.WriteLine("V2.ToString("F", null) = " + V2.ToString("F", null)); Console.WriteLine("V2.ToString("D", new CultureInfo("fr-BE")) = " + V2.ToString("D", new CultureInfo("fr-BE"))); //French - Belgium Console.WriteLine("V2.ToString("F", new CultureInfo("fr-BE")) = " + V2.ToString("F", new CultureInfo("fr-BE"))); //French - Belgium Console.WriteLine(" Test overloaded ToString("V", formatProvider) method"); Console.WriteLine("V2.ToString("V", Formatter) = " + V2.ToString("V", Formatter)); Console.WriteLine("V2.ToString("V", null) = " + V2.ToString("V", null)); }
This code displays the following results:
Test Default ToString method V1 = (0,0) (40,123) V2 = (0,-2) (1,11) V1.ToString( ) = (0,0) (40,123) V2.ToString( ) = (0,-2) (1,11) Test overloaded ToString(format) method V1.ToString("D") = (0,0)(40,123) V1.ToString("D5") = (00000,00000)(00040,00123) V2.ToString("F") = (0.00,-2.00)(1.00,11.00) V1.ToString("N") = (0.00,0.00)(40.00,123.00) V2.ToString("n") = (0.00,-2.00)(1.00,11.00) V1.ToString("E") = (0.000000E+000,0.000000E+000)(4.000000E+001,1.230000E+002) V2.ToString("X") = (0,FFFFFFFE)(1,B) Test overloaded ToString(formatProvider) method V2.ToString(Formatter) = (0,!2)(1,11) V2.ToString(Formatter) = (0,!2)(1,11) V2.ToString(null) = (0,-2)(1,11) V2.ToString(null) = (0,-2)(1,11) V2.ToString(new CultureInfo("fr-BE")) = (0,-2)(1,11) V2.ToString(new CultureInfo("fr-BE")) = (0,-2)(1,11) Test overloaded ToString(format, formatProvider) method V2.ToString("D", Formatter) = (0,!2)(1,11) V2.ToString("F", Formatter) = (0.00,!2.00)(1.00,11.00) V2.ToString("D", null) = (0,-2)(1,11) V2.ToString("F", null) = (0.00,-2.00)(1.00,11.00) V2.ToString("D", new CultureInfo("fr-BE")) = (0,-2)(1,11) V2.ToString("F", new CultureInfo("fr-BE")) = (0,00,-2,00)(1,00,11,00) Test overloaded ToString("V", formatProvider) method V2.ToString("V", Formatter) = magnitude = 3.6109 direction = 1.494 V2.ToString("V", null) = magnitude = 3.6109 direction = 1.494
This method prints out the two x
and
y
coordinates that make up the start and end
points of a line for the Line
class. An example
output of the Line.ToString( )
method is:
(0,0) (40,123)
This output could also be displayed as a vector that starts at the
origin of the Cartesian plane and points straight up along the
positive y-axis. Another choice for this would be to print out the
magnitude and direction of this line. This result is demonstrated in
the overloaded ToString
method that accepts both a
format
string and an
IFormatProvider
.
The next overloaded ToString
method takes a single
argument, format
, which is a
String
containing the formatting information of
the type. This method calls the last overloaded
ToString
method and passes the format information
as the first parameter and a null
as the second
parameter. The following ToString
method operates
similarly to the previous ToString
method, except
that it accepts an IFormatProvider
data type as
its only parameter. The format parameter of the last
ToString
method is set to null
when called by this method.
The final ToString
method is where all the real
work takes place. This method accepts two parameters, a
String
(format
) containing
formatting information and an IFormatProvider
(formatProvider
) containing even more specific
formatting information. The format string makes use of predefined
formats such as “D”,
“d”,
“F”,
“f”,
“G”,
“g”,
“X”, and
“x”, to name a few. (See Recipe 2.16 for more information on the formatting
character codes.) These formats specify whether the information will
be displayed as decimal (“D” or
“d”), general
(“G” or
“g”), hexadecimal
(“X” or
“x”), or one of the other types. As
a note, calling ToString
with no parameters always
sets the format type to general. In addition, this method also takes
a special format character “V” or
“v”. This character formatting code
is not one of the predefined formatting codes; instead, it is one
that we added to provide special handling of a
Line
object’s output in vector
format. This code allows the Line
type to be
displayed as a magnitude and a direction:
magnitude = 13.038 direction = 1.494
The second parameter accepts any data type that implements the
IFormatProvider
interface. There are three types
in the FCL that implement this interface:
CultureInfo
,
DateTimeFormatInfo
, or
NumberFormatInfo
. The
CultureInfo
class contains formatting information
specific to the various supported cultures that exist around the
world. The DateTimeFormatInfo
class contains
formatting information specific to date and time values; similarly,
the NumberFormatInfo
class contains formatting
information specific to numbers.
This ToString
method sets up a variable,
compositeStr
, which will contain the final
formatted value of the Line
type. Next, the format
parameter is checked for null
. Remember, the
previous ToString
method that accepts the
IFormatProvider
parameter will call this form of
the ToString
method and pass in a
format
value of null
. So we
must be able to handle a null
value gracefully at
this point. If the format
parameter passed in to
the Line
type is not null
and
is equal to the character “V”, we
are able to provide a string to display this line as a magnitude and
a direction. The direction
and
magnitude
values are obtained for this object and
are displayed in a General format with five significant digits of
precision. If, on the other hand, any other type of formatting
character code was passed in—including
null
—each of the individual coordinates are
formatted using the ToString
method of the
Int32
structure. These coordinates are
concatenated into a string and returned to the caller to be
displayed.
The method:
public string ToString(string format, IFormatProvider formatProvider)
must be implemented, since the structure implements the
IFormattable
interface.
The IFormattable
interface provides a consistent
interface for this ToString
method:
public interface IFormattable { string ToString(string format, IFormatProvider formatProvider); }
For the Line
structure, the
IFormattable.ToString
method passes its parameters
to the Int32
structure’s
ToString
method with the same method signature,
which provides a more uniform formatting capability for
Line
values.
Using the IFormattable
interface forces you to
implement the IFormattable.ToString
method to more
effectively display your type’s value(s). However,
you do not have to implement it, as you can see for yourself by
removing this interface from the Line
structure’s declaration. In fact, for
performance’s sake, it is best to not implement this
interface on structures, due to the cost of boxing the structure.
Implementing this interface on a class does not incur a performance
penalty.
See Recipe 2.16; see the “IFormatProvider Interface” topic in the MSDN documentation.