編集: オブジェクトごとに IFormattable を実装する必要はありません...これは、PITA であり、制限が厳しく、メンテナンスの負担がかなり大きくなります。Reflection と IFormatProvider を ICustomFormatter で使用するだけで、どのオブジェクトでも機能します。String.Format には、1 つをパラメーターとして受け取るためのオーバーロードがあります。
これまで考えたこともなかったのですが、あなたに興味をそそられたので、ちょっと試してみました。追加のフォーマット文字列をプロパティ値に渡すことを許可することに注意してください。これは、インデックス付けされていないアクセス可能なプロパティでのみ機能します (ただし、簡単に追加できます)。
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);
}
}
そして、わずかに変更された例:
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);
出力:
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.