{"id":43653,"date":"2014-11-10T07:00:00","date_gmt":"2014-11-10T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2014\/11\/10\/kicking-around-a-function-that-formats-stuff\/"},"modified":"2014-11-10T07:00:00","modified_gmt":"2014-11-10T07:00:00","slug":"kicking-around-a-function-that-formats-stuff","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20141110-00\/?p=43653","title":{"rendered":"Kicking around a function that formats stuff"},"content":{"rendered":"<p>\nToday&#8217;s &#8220;Little Program&#8221; is really a\n&#8220;Little Puzzle&#8221; that got out of hand.\n<\/p>\n<p>\nThis started out as a practical question:\nThis code fragment screams out for some sort of simplification.\n(I&#8217;ve changed the names of the classes.)\n<\/p>\n<pre>\nclass FrogProperty\n{\n public string Name { get; private set; }\n public string Value { get; private set; }\n ...\n}\nclass ToadProperty\n{\n public string Name { get; private set; }\n public string Value { get; private set; }\n ...\n}\nvar frogStuff = new List&lt;string&gt;();\nforeach (var frogProp in FrogProperties) {\n  frogStuff.Add(string.Format(\"{0}: {1}\", frogProp.Name, frogProp.Value));\n}\nfrogStuff.Sort();\nMunge(frogStuff);\nvar toadStuff = new List&lt;string&gt;();\nforeach (var toadProp in ToadProperties) {\n  toadStuff.Add(string.Format(\"{0} = {1}\", toadProp.Name, toadProp.Value));\n}\ntoadStuff.Sort();\nMunge(toadStuff);\nvar catStuff = new List&lt;string&gt;();\nforeach (var cat in Cats) {\n  catStuff.Add(string.Format(\"{0}\", cat.Name));\n}\ncatStuff.Sort();\nMunge(catStuff);\nvar dogStuff = new List&lt;string&gt;();\nforeach (var dogProp in DogProperties) {\n  dogStuff.Add(string.Format(\"{0} {1}\", dogProp.Name, dogProp.Value));\n}\ndogStuff.Sort();\nMunge(dogStuff);\n...\n<\/pre>\n<p>\nClearly, the pattern is\n<\/p>\n<pre>\nvar stuff = new List&lt;string&gt;();\nforeach (var thing in thingCollection) {\n stuff.Add(string.Format(formatstring, thing.Name, [optional: thing.Value]));\n}\nstuff.Sort();\nMunge(stuff);\n<\/pre>\n<p>\nEverything here is pretty straightforward,\nexcept for the\n<code>string.Format<\/code> part.\nCan we write a function that takes a <code>thing<\/code>\nand formats it in a somewhat flexible manner?\n<\/p>\n<p>\nLet&#8217;s start with the\n<code>Name<\/code>-and-<code>Value<\/code> cases.\nWe might try something like this:\n<\/p>\n<pre>\npublic static string FormatNameValue&lt;T&gt;(this T t, string format)\n{\n return string.Format(format, t.Name, t.Value);\n}\n<\/pre>\n<p>\nBut then we&#8217;d run into trouble, because there is no constraint\non <code>T<\/code>, so the compiler will complain,\n&#8220;I don&#8217;t know how to get a <code>Name<\/code> or a\n<code>Value<\/code> from an <code>object<\/code>.&#8221;\n<\/p>\n<p>\nAnd since\n<code>Frog&shy;Property<\/code>\nand\n<code>Toad&shy;Property<\/code>\ndo not have a common base class,\nyou&#8217;re kind of stuck.\n<\/p>\n<p>\nOne way out would be to use the new <code>dynamic<\/code> type:\n<\/p>\n<pre>\npublic static string FormatNameValue&lt;T&gt;(this T t, string format)\n{\n dynamic d = t;\n return string.Format(format, d.Name, d.Value);\n}\n<\/pre>\n<p>\nBut that won&#8217;t work in the <code>Name<\/code>-only case:\n<\/p>\n<pre>\ncat.FormatNameValue(\"{0}\");\n<\/pre>\n<p>\nThe <code>cat<\/code> object has a\n<code>Name<\/code> but no <code>Value<\/code>.\nThe attempt to read the <code>Value<\/code> will raise an exception\n(even though it is never consumed by the format).\n<\/p>\n<p>\nMaybe we can turn to reflection.\n<\/p>\n<pre>\npublic static string FormatNameValue&lt;T&gt;(this T t, string format)\n{\n return string.Format(format,\n                      typeof(T).GetProperty(\"Name\").GetValue(t, null),\n                      typeof(T).GetProperty(\"Value\").GetValue(t, null));\n}\n<\/pre>\n<p>\nThis still raises an exception if there is no <code>Value<\/code>,\nbut we can detect the missing <code>Value<\/code> before we run into\ntrouble with it.\n<\/p>\n<pre>\nstatic object GetPropertyOrNull&lt;T&gt;(this T t, string prop)\n{\n var propInfo = typeof(T).GetProperty(prop);\n return propInfo == null ? null : propInfo.GetValue(t, null);\n}\npublic static string FormatNameValue&lt;T&gt;(this T t, string format)\n{\n return string.Format(format,\n                      t.GetPropertyOrNull(\"Name\"),\n                      t.GetPropertyOrNull(\"Value\"));\n}\n<\/pre>\n<p>\nOkay, now we&#8217;re getting somewhere.\n<\/p>\n<p>\nBut before getting to deep into this exercise, I should point\nout that another way to solve this problem is to turn it\ninside-out.\nInstead of making the munger understand all of the different\nobjects,\nwhy not make each object understand munging?\n<\/p>\n<pre>\nclass FrogProperty : IFormattable\n{\n public string Name { get; private set; }\n public string Value { get; private set; }\n public override ToString(string format, IFormatProvider formatProvider)\n {\n  switch (format) {\n  case \"Munge\":\n   return string.Format(formatProvider,\"{0}: {1}\", Name, Value);\n  default:\n   return ToString(); \/\/ use object.ToString();\n  }\n }\n}\nclass Cat : IFormattable\n{\n public string Name { get; private set; }\n public override ToString(string format, IFormatProvider formatProvider)\n {\n  switch (format) {\n  case \"Munge\":\n   return string.Format(formatProvider,\"{0}\", Name);\n  default:\n   return ToString(); \/\/ use object.ToString();\n  }\n }\n}\n<\/pre>\n<p>\nThe generic helper function would then be\n<\/p>\n<pre>\nvar stuff = new List&lt;string&gt;();\nforeach (var thing in thingCollection) {\n stuff.Add(string.Format(\"{0:Munge}\", thing);\n}\nstuff.Sort();\nMunge(stuff);\n<\/pre>\n<p>\nOkay, fine, rain on my little puzzle parade.\n<\/p>\n<p>\nLet&#8217;s ignore this very useful advice and proceed\nahead with our puzzle,\nbecause <i>we&#8217;re determined to see how far we can go,\neven if it&#8217;s in the wrong direction<\/i>.\n<\/p>\n<p>\nNow that we have\n<code>Format&shy;Name&shy;Value<\/code>,\nwe might say,\n&#8220;What about generalizing to cases where we want\nproperties other than\n<code>Name<\/code> and <code>Value<\/code>?&#8221;\nOne design would be to pass in a format string\nand list of properties you want to fill in:\n<\/p>\n<pre>\nthing.FormatProperties(\"{0}: {1} (modified by {2})\",\n                       \"Name\", \"Value\", \"ModifiedBy\");\n<\/pre>\n<p>\nOur\n<code>Format&shy;Name&shy;Value<\/code>\nfunction would go something like this:\n<\/p>\n<pre>\npublic static string FormatProperties&lt;T&gt;(\n    this T t, string format, params string[] props)\n{\n object[] values = new object[props.Length];\n for (var i = 0; i &lt; props.Length; i++) {\n  values[i] = typeof(T).GetProperty(props[i]).GetValue(t, null);\n }\n return string.Format(format, values);\n}\n<\/pre>\n<p>\nThis suffers from a problem common to most formatters:\nOnce you get more than a few insertions, it becomes\nhard to figure out which one matches up to what.\nSo I&#8217;m going to try something radical:\n<\/p>\n<pre>\nstatic Regex identifier = new Regex(@\"(?&lt;={)(.*?)(?=[:}])\");\n\/\/ <a HREF=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa664670%28v=VS.71%29.aspx\">pedants<\/a> would use\n\/\/identifier = new RegEx(@\"[_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}]\" +\n\/\/       @\"[_\\p{Lu}\\p{Ll}\\p{Lt}\\p{Lm}\\p{Lo}\\p{Nl}\\d\\p{Pc}\\p{Mn}\\p{Mc}]\");\npublic static string FormatProperties&lt;T&gt;(this T t, string format)\n{\n  var values = new ArrayList();\n  int count = 0;\n  format = identifier.Replace(format, (m) =&gt; {\n    values.Add(typeof(T).GetProperty(m.Value).GetValue(t, null));\n    return (count++).ToString();\n  });\n  return string.Format(format, values.ToArray());\n}\n<\/pre>\n<p>\nInstead of separating the properties from the format,\nI embed them in the format.\n<\/p>\n<pre>\nthing.FormatProperties(\"{Name}: {Value} (modified by {ModifiedBy})\");\n<\/pre>\n<p>\nNote that I explicitly exclude colons from identifiers.\nThat lets me do things like this:\n<\/p>\n<pre>\nvar result =\n  (new System.IO.FileInfo(@\"C:\\Windows\\Explorer.exe\"))\n    .FormatProperties(\"Created on {CreationTime:F} \" +\n                      \"{Length} bytes in size\");\n<\/pre>\n<p>\nThe property names are extracted and replaced with corresponding\nnumbers, but the format string remains,\nallowing it to be used to alter the final formatting of the property.\n<\/p>\n<p>\nOkay, at this point I figured I had gone far enough.\nThe fun had run out,\nso I decided to stop.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Today&#8217;s &#8220;Little Program&#8221; is really a &#8220;Little Puzzle&#8221; that got out of hand. This started out as a practical question: This code fragment screams out for some sort of simplification. (I&#8217;ve changed the names of the classes.) class FrogProperty { public string Name { get; private set; } public string Value { get; private set; [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-43653","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Today&#8217;s &#8220;Little Program&#8221; is really a &#8220;Little Puzzle&#8221; that got out of hand. This started out as a practical question: This code fragment screams out for some sort of simplification. (I&#8217;ve changed the names of the classes.) class FrogProperty { public string Name { get; private set; } public string Value { get; private set; [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/43653","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=43653"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/43653\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=43653"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=43653"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=43653"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}