Edit: You don't have to implement IFormattable for each object...that'd be a PITA, severely limiting, and a fairly large maintenance burden. Just use Reflection and a IFormatProvider with ICustomFormatter and it'll work with any object. String.Format has an overload to take one as a parameter.
I've never thought of this before, but you intrigued me - so I had to give it a quick whirl. Note that I chose to allow an additional format string to be passed to the property value, and that it only works with non indexed and accessible properties (though you could easily add that).
public class ReflectionFormatProvider : IFormatProvider, ICustomFormatter {
public object GetFormat(Type formatType) {
return formatType == typeof(ICustomFormatter) ? this : null;
}
public string Format(string format, object arg, IFormatProvider formatProvider) {
string[] formats = (format ?? string.Empty).Split(new char[] { ':' }, 2);
string propertyName = formats[0].TrimEnd('}');
string suffix = formats[0].Substring(propertyName.Length);
string propertyFormat = formats.Length > 1 ? formats[1] : null;
PropertyInfo pi = arg.GetType().GetProperty(propertyName);
if (pi == null || pi.GetGetMethod() == null) {
// Pass thru
return (arg is IFormattable) ?
((IFormattable)arg).ToString(format, formatProvider)
: arg.ToString();
}
object value = pi.GetGetMethod().Invoke(arg, null);
return (propertyFormat == null) ?
(value ?? string.Empty).ToString() + suffix
: string.Format("{0:" + propertyFormat + "}", value);
}
}
And your slightly modified example:
var p1 = new Person() {Name="John", NumberOfCats=0, TimeTheyWillDie=DateTime.Today};
var p2 = new Person() {Name="Mary", NumberOfCats=50, TimeTheyWillDie=DateTime.MaxValue};
var str = string.Format(
new ReflectionFormatProvider(),
@"{0:Name} has {0:NumberOfCats} cats and {1:Name} has {1:NumberOfCats} cats.
They will die {0:TimeTheyWillDie:MM/dd/yyyy} and {1:TimeTheyWillDie} respectively.
This is a currency: {2:c2}.",
p1,
p2,
8.50M
);
Console.WriteLine(str);
Outputs:
John has 0 cats and Mary has 50 cats.
They will die 12/10/2008 and 12/31/9999 11:59:59 PM respectively.
This is a currency: $8.50.