Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to provide custom string placeholder for string format

You might want to check out FormatWith 2.0 by James Newton-King. It allows you to use property names as formatting tokens such as this:

var user = new User()
{
    Name = "Olle Wobbla",
    Age = 25
};

Console.WriteLine("Your name is {Name} and your age is {Age}".FormatWith(user));

You can also use it with anonymous types.

UPDATE: There is also a similar solution by Scott Hanselman but it is implemented as a set of extension methods on Object instead of String.

UPDATE 2012: You can get Calrius Consulting's NETFx String.FormatWith Extension Method NuGet package on NuGet.org

UPDATE 2014: There is also StringFormat.NET and littlebit's StringFormat


Regex with a MatchEvaluator seems a good option:

static readonly Regex re = new Regex(@"\{([^\}]+)\}", RegexOptions.Compiled);
static void Main()
{
    string input = "this {foo} is now {bar}.";
    StringDictionary fields = new StringDictionary();
    fields.Add("foo", "code");
    fields.Add("bar", "working");

    string output = re.Replace(input, delegate (Match match) {
        return fields[match.Groups[1].Value];
    });
    Console.WriteLine(output); // "this code is now working."
}

I saw all the answers above, yet, couldn't get the question right :)

Is there any particular reason why the following code does not meet your requirement?

string myFirstStr = GetMyFirstStrFromSomewhere();
string mySecondStr = GetMySecondStrFromSomewhere();

string result = "Enter " + myFirstStr + " " + mySecondStr + " name";

object[] myInts = new int[] {8,9}; 

However you can get away with:

object[] myInts = new string[] { "8", "9" }; 
string bar = string.Format("{0} {1}", myInts); 

Here's another version of this that I found here: http://www.reddit.com/r/programming/comments/bodml/beef_up_params_in_c_5_to_solve_lambda_abuse/c0nrsf1

Any solution to this is going to involve reflection, which is less than ideal, but here's his code with some of the other major performance issues resolved. (No error checking. Add it if you like.):

1) Uses direct runtime reflection, no DataBinder overhead

2) Doesn't use regular expressions, uses a single-pass parse and state.

3) Doesn't convert the string into an intermediate string and then convert it again to the final format.

4) Allocates and concatenates with a single StringBuilder instead of newing up strings all over the place and concatenating them into new strings.

5) Removes the stack overhead of calling a delegate for n replace operations.

6) In general is a single pass through that will scale in a relatively linear manner (still some cost for each prop lookup and nested prop lookup, but that's that.)

public static string FormatWith(this string format, object source)
{
    StringBuilder sbResult = new StringBuilder(format.Length);
    StringBuilder sbCurrentTerm = new StringBuilder();
    char[] formatChars = format.ToCharArray();
    bool inTerm = false;
    object currentPropValue = source;

    for (int i = 0; i < format.Length; i++)
    {
        if (formatChars[i] == '{')
            inTerm = true;
        else if (formatChars[i] == '}')
        {
            PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
            sbResult.Append((string)(pi.PropertyType.GetMethod("ToString", new Type[]{}).Invoke(pi.GetValue(currentPropValue, null), null)));
            sbCurrentTerm.Clear();
            inTerm = false;
            currentPropValue = source;
        }
        else if (inTerm)
        {
            if (formatChars[i] == '.')
            {
                PropertyInfo pi = currentPropValue.GetType().GetProperty(sbCurrentTerm.ToString());
                currentPropValue = pi.GetValue(source, null);
                sbCurrentTerm.Clear();
            }
            else
                sbCurrentTerm.Append(formatChars[i]);
        }
        else
            sbResult.Append(formatChars[i]);
    }
    return sbResult.ToString();
} 

You can also use the example from Marc Gravell and Extend the String class object:

public static class StringExtension
{
    static readonly Regex re = new Regex(@"\{([^\}]+)\}", RegexOptions.Compiled);
    public static string FormatPlaceholder(this string str, Dictionary<string, string> fields)
    {
        if (fields == null)
            return str;

        return re.Replace(str, delegate(Match match)
        {
            return fields[match.Groups[1].Value];
        });

    }
}

Example usage:

String str = "I bought a {color} car";
Dictionary<string, string> fields = new Dictionary<string, string>();
fields.Add("color", "blue");

str.FormatPlaceholder(fields));