Click here to Skip to main content
14,973,970 members
Articles / General Programming / String
Alternative
Tip/Trick
Posted 6 Jul 2012

Stats

10.5K views
8 bookmarked

Custom String FormatWith using Reflection

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
9 Jul 2012CPOL1 min read
This is an alternative for "Custom String FormatWith using Reflection"

Background

posted a Tip that was pretty cool, but forfeited the value-formatting ability of string.Format(). I thought there ought to be a way to accomplish both.

Using the code

This alternative is called in the same manner as the original Tip. I added an overload that takes an IFormatProvider to inform the formatting. This example is extended from the original Tip:

C#
 UserInformation user = new UserInformation {
        FirstName = "Joe", 
        LastName = "Doe", 
        Address1 = "Joe's House", 
        City = "San    Jose", 
        Zipcode = "94101", 
        Email = "joe@doe.com", 
        PhoneNumber = 408000000
      };

      var userInfoXml = @"
<userinfo>
  <firstname>{FirstName,15}</firstname>
  <lastname>{LastName,-15}</lastname>
  <email>{{{Email}}}</email>
  <phone>{{PhoneNumber}}--{PhoneNumber:N0}</phone>
</userinfo>";

      Console.WriteLine(userInfoXml.FormatWithObject(user));

Which displays:

<userinfo>
  <firstname>            Joe</firstname>
  <lastname>Doe            </lastname>
  <email>{joe@doe.com}</email>
  <phone>{PhoneNumber}--408,000,000</phone>
</userinfo>

All of the normal Composite Formatting should work correctly.

Implementation

I changed this to rewrite the input string as a normal composite formatting string and then just called string.Format().

I simplified the construction of propertyNamesAndValues.

There is special case checking for the doubled curley braces ({{ or }}) appearing in the input string. They are subtituted out for characters that are "safe" (i.e. don't appear in the string) before rewriting the formatting string, and are restored before calling string.Format(). See the {{{Email}}} and {{PhoneNumber}} above. (This can be comparatively expensive!)

C#
public static class CustomStringFormattingExtensionMethods
{
  public static string FormatWithObject(this string str, object o)
  {
    return FormatWithObject(str, o, CultureInfo.CurrentCulture);
  }
  public static string FormatWithObject(this string str, object o, IFormatProvider formatProvider)
  {
    if (o == null)
      return str;
    var propertyNamesAndValues = o.GetType()
      .GetProperties()
      .Where(pi => pi.CanRead)
      .Select(pi => new {
        pi.Name,
        Value = pi.GetValue(o, null)
      });

    char substLeftDouble = '\0';              // **very** unlikely
    char substRightDouble = substLeftDouble;  // initially equal
    if (str.Contains("{{") || str.Contains("}}"))
    {
      var strAndDigits = "0123456789" + str;
      while (strAndDigits.Contains(++substLeftDouble));
      substRightDouble = substLeftDouble;
      while (strAndDigits.Contains(++substRightDouble));
      str = Regex.Replace(str, "{{", new string(substLeftDouble, 1));
      str = Regex.Replace(str, "}}", new string(substRightDouble, 1), RegexOptions.RightToLeft);
    }

    var index = 0;
    foreach (var pnv in propertyNamesAndValues)
    {
      //str = str.Replace("{" + pnv.Name, "{" + index.ToString(CultureInfo.InvariantCulture));
      str = Regex.Replace(str, "{" + pnv.Name + @"\b", "{" + index.ToString(CultureInfo.InvariantCulture));
      index++;
    }
    if (substRightDouble != substLeftDouble)  // if they differ, then we need to handle this case
    {
      str = str.Replace(new string(substLeftDouble, 1), "{{").Replace(new string(substRightDouble, 1), "}}");
    }
    // this depends on the Select enumerating in the same order as foreach
    return string.Format(formatProvider, str, propertyNamesAndValues.Select(p => p.Value).ToArray());
  }
}

Points of Interest

I started by using Regex heavily and kept refining it to simpler forms. Dealing with the doubled braces is kind of ugly, but I couldn't think of something simpler. strAndDigits is necessary because we're about to put numbers into the formatting string and must avoid using digits as the substitution characters.

This probably will not behave well if any property name is a proper prefix of another property name. This was fixed by changing the str.Replace() in the foreach to Regex.Replace() and adding the "\b" word boundary anchor to the match pattern.

History

  • July 7, 2012 Initial posting.
  • July 8, 2012 Updated to actually USE the IFormatProvider argument.
  • July 9, 2012 Fixed to work correctly if a property name is a proper prefix of another property name.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Matt T Heffron
Software Developer (Senior) Sciex
United States United States
I started programming in Basic on a DECSystem-10 as a Freshman at Caltech in 1974. I quickly transitioned to assembly language, Fortran, and Pascal. As a summer job at JPL, I did analysis of fuel consumption for the Viking Mars Orbiter attitude control system. I also spent a summer doing O/S maintenance at Digital Equipment Corporation.
After graduation, I started developing microprocessor development tools (e.g., cross-compiler, debugger) for Beckman Instruments, a scientific instrument company.
I've worked on custom file-systems, a real-time O/S for Z8000, Expert Systems (SpinPro & PepPro), and internal and external networking support (I was their first webmaster).
I've worked on the DNA analysis system.
I was the console/UI software architect for Ultracentrifuges and protein Capillary Electrophoresis systems.
After 35 years, Danaher having acquired Beckman (now Beckman Coulter), transferred the CE group to become part of Sciex (2014).

Comments and Discussions

 
GeneralMy vote of 5 Pin
bobfox15-Jul-12 13:34
professionalbobfox15-Jul-12 13:34 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.