views:

466

answers:

4

I need to create a very long string in a program, and have been using String.Format. The problem I am facing is keeping track of all the numbers when you have more than 8-10 parameters.

Is it possible to create some form of overload that will accept a syntax similar to this?

String.Format("You are {age} years old and your last name is {name} ",
{age = "18", name = "Foo"});
+2  A: 

not quite the same but sort of spoofing it... use an extension method, a dictionary and a little code:

something like this...

  public static class Extensions {

        public static string FormatX(this string format, params KeyValuePair<string, object> []  values) {
            string res = format;
            foreach (KeyValuePair<string, object> kvp in values) {
                res = res.Replace(string.Format("{0}", kvp.Key), kvp.Value.ToString());
            }
            return res;
        }

    }
Preet Sangha
Extension method does not work, String.Format is static. But you can just create a new static method.
Stefan Steinegger
sure you're right! Just another top of my head go...
Preet Sangha
+1  A: 

primitive implementation:

public static class StringUtility
{
  public static string Format(string pattern, IDictionary<string, object> args)
  {
    StringBuilder builder = new StringBuilder(pattern);
    foreach (var arg in args)
    {
      builder.Replace("{" + arg.Key + "}", arg.Value.ToString());
    }
    return builder.ToString();
  }
}

Usage:

StringUtility.Format("You are {age} years old and your last name is {name} ",
  new Dictionary<string, object>() {{"age" = 18, "name" = "Foo"}});

You could also use a anonymous class, but this is much slower because of the reflection you'll need.

For a real implementation you should use regular expression to

  • allow escaping the {}
  • check if there are placeholders that where not replaced, which is most probably a programming error.
Stefan Steinegger
+32  A: 

How about the following, which works both for anonymous types (the example below), or regular types (domain entities, etc):

static void Main()
{
    string s = Format("You are {age} years old and your last name is {name} ",
        new {age = 18, name = "Foo"});
}

using:

static readonly Regex rePattern = new Regex(
    @"(\{+)([^\}]+)(\}+)", RegexOptions.Compiled);
static string Format(string pattern, object template)
{
    if (template == null) throw new ArgumentNullException();
    Type type = template.GetType();
    var cache = new Dictionary<string, string>();
    return rePattern.Replace(pattern, match =>
    {
        int lCount = match.Groups[1].Value.Length,
            rCount = match.Groups[3].Value.Length;
        if ((lCount % 2) != (rCount % 2)) throw new InvalidOperationException("Unbalanced braces");
        string lBrace = lCount == 1 ? "" : new string('{', lCount / 2),
            rBrace = rCount == 1 ? "" : new string('}', rCount / 2);

        string key = match.Groups[2].Value, value;
        if(lCount % 2 == 0) {
            value = key;
        } else {
            if (!cache.TryGetValue(key, out value))
            {
                var prop = type.GetProperty(key);
                if (prop == null)
                {
                    throw new ArgumentException("Not found: " + key, "pattern");
                }
                value = Convert.ToString(prop.GetValue(template, null));
                cache.Add(key, value);
            }
        }
        return lBrace + value + rBrace;
    });
}
Marc Gravell
excellent - I like it!
Preet Sangha
I didn't now you could use anonymous types like this. It's not just .net 4 is it?
Preet Sangha
Plus it'll work for domain entities, i.e. `Format("Dear {Title} {Forename},...", person)`
Marc Gravell
@Preet - C# 3.0, so VS2008 or the .NET 3.5 compiler, but fine targetting .NET 2.0
Marc Gravell
thank you. It's a lovely bit of language usage!
Preet Sangha
There's security concerns to think about here, if the values contain other `{Format}` blocks.
Jason
How does that present a security concern? Care to give an example?
Marc Gravell
Here's one, in a server-side call: Format("/resource/to/post?username={User} If message contains {Key}, then you've revealed your API Key to the user/public, or something similar.
Jason
+1/+1 Is there a way to nominate functionality for the next version of the .Net FCL? If so, count me in, This is a great question and solution!
Paul Sasik
@Jason - that is a circular argument; if you have code that chooses to format (and hence output) sensitive data, then it doesn't matter **what** approach you use to do it...
Marc Gravell
Stellar, nicest bit of code I've seen in a long while :)
Binary Worrier
This works like a charm, very good. Did you come up with this just of the top of your head? Maybe you should include in the answer itself that is also works for domain entities.
Espo
The pattern itself is used quite a bit in things like ASP.NET MVC, and was presumably inspired by the way that jQuery allows you to specify multiple options on an object in javascript.
Marc Gravell
amazing, i had no idea that was possible (new{var=woot, var2=moreWoot})
acidzombie24
There's a bug in this code, it doesn't handles escaped braces. Try with this format for instance: "{{age}} = {age}, {{name}} = {name}". I had a question about how to solve that issue: http://stackoverflow.com/questions/1445571/regular-expression-for-string-format-like-utility. I eventually found a solution, but I didn't post it because I wasn't really happy with it...
Thomas Levesque
Well, I could argue it is an unsupported usage ;-p But it should be possible to do that just by changing the regex?
Marc Gravell
Fixed it for you...
Marc Gravell
cool, thanks :)
Thomas Levesque
+1  A: 

What about if age/name is an variable in your application. So you would need a sort syntax to make it almost unique like {age_1}?

If you have trouble with 8-10 parameters: why don't use

"You are " + age + " years old and your last name is " + name + "
PoweRoy
+1 for the simplicity and willingness to buck the string.Format requirement in the original question. Though i do like Marc Gravell's solution.
Paul Sasik
In my simple example, you could, but when you are outputting say, HTML, it gets even harder to read. string test = "<input type=""" + age + """ name=""" + name + """ />";
Espo
so true, it really depends on the usage. Even String.Format with html is crappy to read
PoweRoy
Absolutely un-localizable!
Serge - appTranslator
yeah but there's no requirement so why bother... Making complex things just to make it possible easier later on, it's just wasting time.
PoweRoy