This is the last of four
parts this article consists of:
Introduction - Part in which proposed goals and desired features of the solution are defined.
Design - Part in which solution design is outlined, explaining critical details that the solution will have to implement.
Implementation - Third part which explains actual implementation of the formatter classes.
Example - Final part which lists numerous examples of formatters use.
The last part of the article
has a compressed file attached with it, containing complete and commented source
code of the formatter classes, source code of the demonstration project and
compiled library.
How to Use
VerboseFormatInfoBase and
VerboseFormatInfo classes implement IFormatProvider interface which implies that
they can be used as any other .NET format provider. For example, this line of
code will print the content of a Point structure:
Console.WriteLine(string.Format(new
VerboseFormatInfo(), "{0}",
new Point(3, 4)));
Output produced by this line
of code is:
Point {bool IsEmpty=false, int X=3, int Y=4}
More options are available if
one allocates the formatter before its use and then set its properties to
appropriate values:
VerboseFormatInfo vfi =
new
VerboseFormatInfo();
vfi.FieldDelimiter = "; ";
vfi.FirstContainedValuePrefix = "(";
vfi.LastContainedValueSuffix = ")";
vfi.InstanceName = "center";
Console.WriteLine(string.Format(vfi,
"{0}", new
Point(3, 4)));
This would produce
somewhat different output:
Point center (bool IsEmpty=false; int X=3; int
Y=4)
However, caller doesn't have
to bother too much setting up formatter's properties. VerboseFormatInfo class
exposes static properties that already have pre-set values of all relevant
properties: SingleLinedFormat, SimpleFormat, MultiLinedFormat,
TabbedMultiLinedFormat and TreeMultiLinedFormat. These properties return
VerboseFormatInfoBase instances that are ready to be used to format strings:
Console.WriteLine(string.Format(VerboseFormatInfo.SimpleFormat,
"{0}", new
Point(3, 4)));
Even shorter notation is
available, as VerboseFormatInfoBase class exposes Format method which receives a
single object, which is the object being formatted, and returns formatted
string. Hence, the same effect as with line above can be achieved with this
code:
Console.WriteLine(VerboseFormatInfo.SimpleFormat.Format(new
Point(3, 4)));
In the following section we
will demonstrate full power of the VerboseFormatInfo class. Up to this point we
have already seen one of its most powerful features - simplicity. Caller is
almost never expected to do anything regarding string formatting, apart from
simply instantiating appropriate form of the verbose formatter. The formatter
can then be used by either providing it to the String.Format method or by
calling its Format method directly.
For instance, ToString method
of the VerboseFormatInfoBase class (and hence all classes derived from it) looks
like this:
public
override string
ToString()
{
return
FormatInfoUtils.CreateSimpleFormatter().Format(this)
}
In this code FormatInfoUtils
is the static
class internally
used by verbose formatters. In general case,
ToString method of any class
can be
implemented using the same pattern:
public
override string
ToString()
{
return
VerboseFormatInfo.SimpleFormat.Format(this);
}
Thanks to this single-liner,
any VerboseFormatInfo class instance can be simply formatted into string without
any additional effort:
Console.WriteLine(VerboseFormatInfo.TreeMultiLinedFormat);
This code provides quite
verbose description of the TreeMultiLinedFormat instance:
VerboseFormatInfo {string FieldDelimiter="\r\n", string FirstContainedValuePrefix="{", int IndentationLevel=0, string IndentationString=" | ", Type InstanceDataType=null, string InstanceName=null, bool IsMultiLinedFormat=true, string LastContainedValueSuffix="}", string LastIndentationString=" | ", string LastRightMostIndentationString=" +-- ", string LinePrefix="", int MaximumDepth=5, int MaximumFormattedLength=-1, int RawMaximumFormattedLength=80, string RightMostIndentationString=" |-- ", bool ShowDataType=true, bool ShowInstanceName=true, VerboseFormatInfo ValueOnly={}}
Just try to imagine how much
coding it would require to produce the same output manually, let alone errors
that would probably arise here and there in this long string. If one is not
satisfied with such long output, the same instance can be formatted in a bit
different way:
VerboseFormatInfoBase vfi =
VerboseFormatInfo.TreeMultiLinedFormat;
vfi.MaximumDepth = 1;
Console.WriteLine(vfi.Format(vfi));
And this is the output
produced:
VerboseFormatInfo {
|-- string FieldDelimiter="\r\n"
|-- string FirstContainedValuePrefix="{"
|-- int IndentationLevel=0
|-- string IndentationString=" | "
|-- Type InstanceDataType=null
|-- string InstanceName=null
|-- bool IsMultiLinedFormat=true
|-- string LastContainedValueSuffix="}"
|-- string LastIndentationString=" | "
|-- string LastRightMostIndentationString=" +-- "
|-- string LinePrefix=""
|-- int MaximumDepth=1
|-- int MaximumFormattedLength=-1
|-- int RawMaximumFormattedLength=80
|-- string RightMostIndentationString=" |-- "
|-- bool ShowDataType=true
|-- bool ShowInstanceName=true
+-- VerboseFormatInfo ValueOnly={}}Depending on visual settings,
i.e. the viewer and fonts used, this form could be much easier to read.
Examples
In this section we will
present several examples of VerboseFormatInfo class use. First we will start
with very simple case of an array of characters:
char[]
charArray = new char[]
{ 'A', '\n',
'\t', (char)8,
's', 'o',
'm', 'e',
't', 'h',
'i', 'n',
'g' };
Console.WriteLine(VerboseFormatInfo.SimpleFormat.Format(charArray));
This code produces output:
char[13] {'A', '\n', '\t', 0x08, 's', 'o', 'm',
'e', 't', 'h', 'i', 'n', 'g'}
Output shows that type of the
formatted object is array of characters of dimension 13. Further on, all
characters are presented, conveniently showing special new line, tab and
backspace character (ASCII code 8) differently from plain letters. It is worthy
to notice that exactly the same output is produced when MultiLinedFormat is
used:
Console.WriteLine(VerboseFormatInfo.MultiLinedFormat.Format(charArray));
Output is the same because
VerboseFormatInfo class has first tried to inline the array, and that attempt
ended in a success. So there was no reason to break lines and to write the
output over multiple lines.
char[13] {'A', '\n', '\t', 0x08, 's', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g'}
However, next example is
not that simple:
string[]
text = new string[]
{
"The",
"following", "code",
"example",
"demonstrates", "the",
"implementation",
"of", "the",
"IEnumerable",
"and", "IEnumerator",
"interfaces",
"for", "a",
"custom",
"collection.", "In",
"this",
"example,",
"members", "of", "these",
"interfaces",
"are", "not",
"explicitly",
"called,", "but", "they",
"are",
"implemented",
"to", "support",
"the", "use",
"of", "foreach",
"(For", "Each",
"in", "Visual",
"Basic)", "to",
"iterate",
"through", "the",
"collection."
};
Console.WriteLine(VerboseFormatInfo.MultiLinedFormat.Format(text));
In this example we are
printing out array of strings (which is actually a paragraph copied from MSDN
online edition). Output would in this case look as follows:
string[48] {"The" "following" "code" "example"
"demonstrates" "the" "implementation" "of"
"the" "IEnumerable" "and" "IEnumerator"
"interfaces" "for" "a" "custom"
"collection." "In" "this" "example,"
"members" "of" "these" "interfaces"
"are" "not" "explicitly" "called,"
"but" "they" "are" "implemented"
"to" "support" "the" "use"
"of" "foreach" "(For" "Each"
"in" "Visual" "Basic)" "to"
"iterate" "through" "the" "collection." }Verbose formatter has
determined that content is so long that it cannot be written in one line.
Consequently, output has been broken over several lines, strictly taking care
that none of the lines is longer than 80 characters. All values printed are
aligned so that longest string fits into the layout. This example has shown that
MultiLinedFormat is more flexible than SingleLinedFormat and it should be used
in cases when output may freely span over multiple lines if it is too long.
Next example will be the
jagged array:
int
jaggedArrayRows = 12;
int[][]
jaggedIntArray = new
int[jaggedArrayRows][];
for
(int i = 0; i < jaggedArrayRows; i++)
{
int jaggedArrayCols = rnd.Next(40) + 1;
jaggedIntArray[i] =
new int[jaggedArrayCols];
for (int j = 0; j < jaggedArrayCols; j++)
jaggedIntArray[i][j] =
rnd.Next(1000);
}
Console.WriteLine(VerboseFormatInfo.MultiLinedFormat.Format(jaggedIntArray));
Here we are creating a jagged
array with 12 rows and variable number of columns. We are using MultiLinedFormat
again to produce the following output:
int[12][3-33] {
Row 0 {465 618 681 359 895 451 40 869 525 435 576 620 975 965 589 939 324}
Row 1 {861 542 181 461 972 568 23 675 534 662 594 55 242 619 736 523 22 350 967 414
338 571 559 303 687 948}
Row 2 {261 570 438 525 262 756 167 101 906 218 3 645 113 336 219 916 377 274 470 192
957 883 451 942 239 972 910 305 290 983 878 773 24 }
Row 3 {761 105 96 137 332 299 75 517 830 304 18 704 853 78 118 654 181 29 217 755
853 378 118 495 285 290 845 861 811 923 340}
Row 4 {621 355 574 114 820 239 941 171 117 439}
Row 5 {147 923 535}
Row 6 {832 187 636 801 849}
Row 7 {269 904 383 987 600 852 42 453 213 706 906 963 788 82 497 503 4 834 942 87
45 637 413}
Row 8 {971 572 750 171 12 674 175 121 491 673 352 451 191 127}
Row 9 {387 899 718 904 733 146 183 134 452}
Row 10 {328 182 802 859 762 511 631 490 57 15 28 102 870 440 539 531 784 472 115 730
46 312 877 613 554}
Row 11 {326 733 680 179 787 437 297 550}}Columns are again aligned,
although some lines are so long that they must be broken into multiple lines. At
this point we will demonstrate the use of TreeMultiLinedFormat property:
Console.WriteLine(VerboseFormatInfo.TreeMultiLinedFormat.Format(jaggedIntArray));
Output is now different:
int[12][1-39] {
|-- Row 0 {589 142 240 99 461 910 998 406 267 430 724 960 316 242 752 224 416 611 879 121
| 195 889 118 378 934 776 187 939 229 739 869 401 748 588 144 502}
|-- Row 1 {529 720 159 538 440 760 936 185 721 737 771 148 949 781 86 678 606 699 699 23
| 862 165 685 722 59 176 528 561 323 212 727 98 250 255 886 890 720 582 754}
|-- Row 2 {182 192 213 38 416 790}
|-- Row 3 {957 782 723 141 888 444 479 107 364 35 436 479}
|-- Row 4 {232 375 861 509 10 672 898 694 894 195 957}
|-- Row 5 {945}
|-- Row 6 {841 670 952 646 306 268 896 570 778 600 70 838 654 770 148 522 854 283 103 490
| 896 806 330}
|-- Row 7 {28 743 892 585 63 587 765 117 943 873 637 436 412 388 130 210 367 661 597 261
| 438 171 18 127 546 372 340 674 921}
|-- Row 8 {673 34 339 248 618 563 376 310 507 191 835 953 895 780 132 711 109 466 153}
|-- Row 9 {122 235 209 69 265 856 724 765 39 690 246 91 196 487 199 603 97 164 770 566
| 833 57 154 406 426 484 275 237 994 835 262 874 520 27 333 438}
|-- Row 10 {270 983 761 838 611 271 816 944 743 757 407 581 529 613 945 696 586 59 64 81
| 803 642 780 581 487 770 203 428 371 570 168 154 760 778}
+-- Row 11 {181 8 728 222 785 337 461 682 727 867 656 464 221 316 178 933 968 268 357 21
627 202 180 351 840 67 388 573 173 589 253 820 751 785}}However, full power of the
TreeMultiLinedFormat can be seen when applied to more complex objects. In the
next example we will create a hash table and fill it with number of mixed Point
and Rectangle structures:
Hashtable hash =
new Hashtable();
int
dictSize = 9;
for
(int i = 0; i < dictSize; i++)
{
int key = 0;
do
{
key = rnd.Next(1000);
}
while (dict.ContainsKey(key));
Point value = new Point(rnd.Next(100),
rnd.Next(70));
object objValue = value;
if (key % 3 == 0)
objValue = new Rectangle(value,
new Size(rnd.Next(90), rnd.Next(30)));
hash.Add(key, objValue);
}
Console.WriteLine(VerboseFormatInfo.TreeMultiLinedFormat.Format(hash));
Resulting object is
relatively complex and we are printing it out using TreeMultiLinedFormat to
produce the following output:
Hashtable = {
|-- Item[0] = {
| |-- int Key=100
| +-- Point Value={bool IsEmpty=false, int X=39, int Y=49}}
|-- Item[1] = {
| |-- int Key=470
| +-- Point Value={bool IsEmpty=false, int X=30, int Y=35}}
|-- Item[2] = {
| |-- int Key=979
| +-- Point Value={bool IsEmpty=false, int X=5, int Y=43}}
|-- Item[3] = {
| |-- int Key=893
| +-- Point Value={bool IsEmpty=false, int X=33, int Y=51}}
|-- Item[4] = {
| |-- int Key=567
| +-- Rectangle Value = {
| |-- int Bottom=34
| |-- int Height=27
| |-- bool IsEmpty=false
| |-- int Left=81
| |-- Point Location={bool IsEmpty=false, int X=81, int Y=7}
| |-- int Right=164
| |-- Size Size={int Height=27, bool IsEmpty=false, int Width=83}
| |-- int Top=7
| |-- int Width=83
| |-- int X=81
| +-- int Y=7}}
|-- Item[5] = {
| |-- int Key=69
| +-- Rectangle Value = {
| |-- int Bottom=26
| |-- int Height=20
| |-- bool IsEmpty=false
| |-- int Left=49
| |-- Point Location={bool IsEmpty=false, int X=49, int Y=6}
| |-- int Right=130
| |-- Size Size={int Height=20, bool IsEmpty=false, int Width=81}
| |-- int Top=6
| |-- int Width=81
| |-- int X=49
| +-- int Y=6}}
|-- Item[6] = {
| |-- int Key=921
| +-- Rectangle Value = {
| |-- int Bottom=52
| |-- int Height=1
| |-- bool IsEmpty=false
| |-- int Left=6
| |-- Point Location={bool IsEmpty=false, int X=6, int Y=51}
| |-- int Right=77
| |-- Size Size={int Height=1, bool IsEmpty=false, int Width=71}
| |-- int Top=51
| |-- int Width=71
| |-- int X=6
| +-- int Y=51}}
|-- Item[7] = {
| |-- int Key=801
| +-- Rectangle Value = {
| |-- int Bottom=42
| |-- int Height=3
| |-- bool IsEmpty=false
| |-- int Left=82
| |-- Point Location={bool IsEmpty=false, int X=82, int Y=39}
| |-- int Right=137
| |-- Size Size={int Height=3, bool IsEmpty=false, int Width=55}
| |-- int Top=39
| |-- int Width=55
| |-- int X=82
| +-- int Y=39}}
+-- Item[8] = {
|-- int Key=341
+-- Point Value={bool IsEmpty=false, int X=78, int Y=52}}}This example demonstrates
power of TreeMultiLinedFormat. It can be used to present complex objects in such
way that output becomes quite readable on the screen, even when spanning dozens
of lines. Though only when monospaced fonts are used, so be careful with this
format. Note in the output above that all Point structures have been inlined,
even those which are part of the Rectangle instance. On the other hand, none of
the Rectangle instances has been inlined. All decisions have been made by the
formatter having on mind length of the formatted object - points are producing
short strings and they are inlined; rectangles produce long strings and they are
broken over several lines.
Conclusion
In this article we have
presented set of classes that can be used to convert any object into user
friendly string representation. Conversion results are often not ideal,
sometimes even far from desired, but this method has one significant advantage
on its side: All formatting operations are single-liners. Even with the best of
efforts, custom formatting cannot be performed in such a simple and short way.
However, if custom formatting is still required, it can be applied manually,
leaving less demanding tasks to the verbose formatter. If used in that pattern,
verbose formatter can significantly reduce time needed to address the formatting
issues in code.
Please feel free to download
the source code attached. It contains full definition of all classes from the
verbose formatter solution, as well as test bench project which demonstrates
output produced by verbose formatter when applied to different objects.