views:

4750

answers:

9

Is there any way to format a string by name rather than position in C#?

In python, I can do something like this example (shamelessly stolen from here):

>>> print '%(language)s has %(#)03d quote types.' % \
      {'language': "Python", "#": 2}
Python has 002 quote types.

Is there any way to do this in C#? Say for instance:

String.Format("{some_variable}: {some_other_variable}", ...);

Being able to do this using a variable name would be nice, but a dictionary is acceptable too.

UPDATE: I ended up doing something like this post, but it's definitely not pretty. I'll try out John Sheehan's approach, but if anybody has any other suggestions in the meantime, feel free to add them. :)

UPDATE 2: John Sheehan's approach works pretty well. Accepting it.

+3  A: 

I think the closest you'll get is an indexed format:

String.Format("{0} has {1} quote types.", "C#", "1");

There's also String.Replace(), if you're willing to do it in multiple steps and take it on faith that you won't find your 'variables' anywhere else in the string:

string MyString = "{language} has {n} quote types.";
MyString = MyString.Replace("{language}", "C#").Replace("{n}", "1");

Expanding this to use a List:

List<KeyValuePair<string, string>> replacements = GetFormatDictionary();  
foreach (KeyValuePair<string, string> item in replacements)
{
    MyString = MyString.Replace(item.Key, item.Value);
}

You could do that with a Dictionary<string, string> too by iterating it's .Keys collections, but by using a List<KeyValuePair<string, string>> we can take advantage of the List's .ForEach() method and condense it back to a one-liner:

replacements.ForEach(delegate(KeyValuePair<string,string>) item) { MyString = MyString.Replace(item.Key, item.Value);});

A lambda would be even simpler, but I'm still on .Net 2.0. Also note that the .Replace() performance isn't stellar when used iteratively, since strings in .Net are immutable. Also, this requires the MyString variable be defined in such a way that it's accessible to the delegate, so it's not perfect yet.

Joel Coehoorn
lol exactly what I said in the comment under my post.
Daok
Well, that's not the prettiest solution, but it's what I'm going with for now. The only thing I did differently was use a StringBuilder instead of a string so that I don't keep making new strings.
Jason Baker
+2  A: 

I doubt this will be possible. The first thing that comes to mind is how are you going to get access to local variable names?

There might be some clever way using LINQ and Lambda expressions to do this however.

leppie
@leppie: +1 if you can give me some LINQ+Lambda to do that ;D (ok +1 for having a relevant answer)
sixlettervariables
I would love to see it too! Maybe I will take that challenge!
leppie
I figured it would be impossible to do with variable names, but put that in there in case I was wrong. :)There's not any way to do this with a dictionary either?
Jason Baker
http://community.bartdesmet.net/blogs/bart/default.aspx
leppie
I tried, and got a little somewhere, but I deemed it too ugly and difficult to use. It would have looked like:string s = format(f => f("{hello} {world}", hello, world));
leppie
A: 
string language = "Python";
int numquotes = 2;
string output = language + " has "+ numquotes + " language types.";

Edit: What I should have said was, "No, I don't believe what you want to do is supported by C#. This is as close as you are going to get."

Kevin
I'm curious about the down votes. Anybody want to tell me why?
Kevin
http://stackoverflow.com/questions/16432/c-string-output-format-or-concat
spoulson
So the string.format would perform this operation 4/TenThousandths of a second faster If this function is going to get called a ton you might notice that difference. But it at least answers his question instead of just telling him to do it the same way he already said he didn't want to do it.
Kevin
I didn't vote you down, but I wouldn't implement this mainly because well, I find doing lots of string concatenations ugly. But that's my personal view.
Jason Baker
+22  A: 

There is no built-in method for handling this.

Here's one method

string myString = "{foo} is {bar} and {yadi} is {yada}".Inject(o);

Here's another

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

A third improved method partially based on the two above, from Phil Haack

John Sheehan
I've been very happy using FormatWith(), but wanted to point an in issue I recently came across. The implementation relies on the DataBinder from System.Web.UI, which isn't supported in SQL CLR. Inject(o) doesn't rely on the data binder, which made it useful for multi-token-replace in my SQL CLR object.
EBarr
+3  A: 

There doesn't appear to be a way to do this out of the box. Though, it looks feasible to implement your own IFormatProvider that links to an IDictionary for values.

var Stuff = new Dictionary<string, object> {
   { "language", "Python" },
   { "#", 2 }
};
var Formatter = new DictionaryFormatProvider();

// Interpret {0:x} where {0}=IDictionary and "x" is hash key
Console.WriteLine string.Format(Formatter, "{0:language} has {0:#} quote types", Stuff);

Outputs:

Python has 2 quote types

The caveat is that you can't mix FormatProviders, so the fancy text formatting can't be used at the same time.

spoulson
+6  A: 

The framework itself does not provide a way to do this, but you can take a look at this post by Scott Hanselman. Example usage:

Person p = new Person();  
string foo = p.ToString("{Money:C} {LastName}, {ScottName} {BirthDate}");  
Assert.AreEqual("$3.43 Hanselman, {ScottName} 1/22/1974 12:00:00 AM", foo);

This code by James Newton-King is similar and works with sub-properties and indexes,

string foo = "Top result for {Name} was {Results[0].Name}".FormatWith(student));

James's code relies on System.Web.UI.DataBinder to parse the string and requires referencing System.Web, which some people don't like to do in non-web applications.

EDIT: Oh and they work nicely with anonymous types, if you don't have an object with properties ready for it:

string name = ...;
DateTime date = ...;
string foo = "{Name} - {Birthday}".FormatWith(new { Name = name, Birthday = date });
Lucas
+13  A: 

I have an implementation I just posted to my blog here: http://haacked.com/archive/2009/01/04/fun-with-named-formats-string-parsing-and-edge-cases.aspx

It addresses some issues that these other implementations have with brace escaping. The post has details. It does the DataBinder.Eval thing too, but is still very fast.

Haacked
Nice thanks for sharing Phil!
JoshBerke
The code available for download in that article 404's. I'd really like to see it, too.
qstarin
+2  A: 

See http://stackoverflow.com/questions/271398#358259

With the linked-to extension you can write this:

var str = "{foo} {bar} {baz}".Format(foo=>"foo", bar=>2, baz=>new object());

and you'll get "foo 2 System.Object".

Mark Cidade