Three years ago I postulated about a ToString implementation for C# that seemed useful to me and a few days later I threw it out on the blog. We used in at my old company for a number of things.
Just now I realized that it'd be even more useful (or more evil) with Extension Methods, so I opened up the old project, threw in some MbUnit tests and changed my implementation to use extension methods.
So, like bad take-out food, here it is again, updated as ToString() overloads:
[Test]public void MakeSimplePersonFormattedStringWithDoubleFormatted(){ Person p = new Person(); string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}"); Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);}[Test]public void MakeSimplePersonFormattedStringWithDoubleFormattedInHongKong(){ Person p = new Person(); string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}",new System.Globalization.CultureInfo("zh-hk")); Assert.AreEqual("HK$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);}
It's moderately well covered for all of the hour it took to write it, and I find it useful.
Here's all it is.
public static class FormattableObject { public static string ToString(this object anObject, string aFormat) { return FormattableObject.ToString(anObject, aFormat, null); } public static string ToString(this object anObject, string aFormat, IFormatProvider formatProvider) { StringBuilder sb = new StringBuilder(); Type type = anObject.GetType(); Regex reg = new Regex(@"({)([^}]+)(})",RegexOptions.IgnoreCase); MatchCollection mc = reg.Matches(aFormat); int startIndex = 0; foreach(Match m in mc) { Group g = m.Groups[2]; //it's second in the match between { and } int length = g.Index - startIndex -1; sb.Append(aFormat.Substring(startIndex,length)); string toGet = String.Empty; string toFormat = String.Empty; int formatIndex = g.Value.IndexOf(":"); //formatting would be to the right of a : if (formatIndex == -1) //no formatting, no worries { toGet = g.Value; } else //pickup the formatting { toGet = g.Value.Substring(0,formatIndex); toFormat = g.Value.Substring(formatIndex+1); } //first try properties PropertyInfo retrievedProperty = type.GetProperty(toGet); Type retrievedType = null; object retrievedObject = null; if(retrievedProperty != null) { retrievedType = retrievedProperty.PropertyType; retrievedObject = retrievedProperty.GetValue(anObject,null); } else //try fields { FieldInfo retrievedField = type.GetField(toGet); if (retrievedField != null) { retrievedType = retrievedField.FieldType; retrievedObject = retrievedField.GetValue(anObject); } } if (retrievedType != null ) //Cool, we found something { string result = String.Empty; if(toFormat == String.Empty) //no format info { result = retrievedType.InvokeMember("ToString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase ,null,retrievedObject,null) as string; } else //format info { result = retrievedType.InvokeMember("ToString", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.IgnoreCase ,null,retrievedObject,new object[]{toFormat,formatProvider}) as string; } sb.Append(result); } else //didn't find a property with that name, so be gracious and put it back { sb.Append("{"); sb.Append(g.Value); sb.Append("}"); } startIndex = g.Index + g.Length +1 ; } if (startIndex < aFormat.Length) //include the rest (end) of the string { sb.Append(aFormat.Substring(startIndex)); } return sb.ToString(); }}
Enjoy.
Download Sample: Formatable Objects Extension Methods v0.7
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. I am a failed stand-up comic, a cornrower, and a book author.
Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.