FormatWith 2.0 - String formatting with named variables

The problem with string formatting is that {0}, {1}, {2}, etc, aren't very descriptive. Figuring out what the numbers represent means either deducing their values from their usage context in the string or examining the arguments passed to String.Format. Things become more difficult when you are working with a format string stored in an external resource as the second option no longer available.

FormatWith 2.0

I have updated the original FormatWith with an overload that takes a single argument and allows the use of property names in the format string.

MembershipUser user = Membership.GetUser();
Status.Text = "{UserName} last logged in at {LastLoginDate}".FormatWith(user);

It also works with anonymous types:

"{CurrentTime} - {ProcessName}".FormatWith(new { CurrentTime = DateTime.Now, ProcessName = p.ProcessName });

And even allows for sub-properties and indexes:

var student = new
  Name = "John",
  Email = "",
  BirthDate = new DateTime(1983, 3, 20),
  Results = new[]
    new { Name = "COMP101", Grade = 10 },
    new { Name = "ECON101", Grade = 9 }
Console.WriteLine("Top result for {Name} was {Results[0].Name}".FormatWith(student));
// "Top result for John was COMP101"

There isn't much to the code itself. Most of the heavy lifting is done in a regular expression and the .NET DataBinder class.

To start with a regular expression picks all the {Property} blocks out of the string. The the property expression inside the brackets is then evaluated using the DataBinder class on the object argument to get the property value. That value is then put into a list of values and the {Property} block is replaced in the string with the index of the value in the list, e.g. {1}. Finally the rewritten string, which now looks like any other format string, and the list of evaluated values are passed to String.Format.

public static string FormatWith(this string format, object source)
  return FormatWith(format, null, source);
public static string FormatWith(this string format, IFormatProvider provider, object source)
  if (format == null)
    throw new ArgumentNullException("format");
  Regex r = new Regex(@"(?<start>\{)+(?<property>[\w\.\[\]]+)(?<format>:[^}]+)?(?<end>\})+",
    RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
  List<object> values = new List<object>();
  string rewrittenFormat = r.Replace(format, delegate(Match m)
    Group startGroup = m.Groups["start"];
    Group propertyGroup = m.Groups["property"];
    Group formatGroup = m.Groups["format"];
    Group endGroup = m.Groups["end"];
    values.Add((propertyGroup.Value == "0")
      ? source
      : DataBinder.Eval(source, propertyGroup.Value));
    return new string('{', startGroup.Captures.Count) + (values.Count - 1) + formatGroup.Value
      + new string('}', endGroup.Captures.Count);
  return string.Format(provider, rewrittenFormat, values.ToArray());

kick it on