views:

104

answers:

5

What's the most efficient way to convert a Dictionary to a formatted string.

e.g.:

My method:

public string DictToString(Dictionary<string, string> items, string format){

    format = String.IsNullOrEmpty(format) ? "{0}='{1}' " : format;

    string itemString = "";
    foreach(var item in items){
        itemString = itemString + String.Format(format,item.Key,item.Value);
    }

    return itemString;
}

Is there a better/more concise/more efficient way?

Note: the Dictionary will have at most 10 items and I'm not committed to using it if another similar "key-value pair" object type exists

Also, since I'm returning strings anyhow, what would a generic version look like?

+4  A: 

I just rewrote your version to be a bit more generic and use StringBuilder:

public string DictToString<T, V>(IEnumerable<KeyValuePair<T, V>> items, string format)
{
    format = String.IsNullOrEmpty(format) ? "{0}='{1}' " : format; 

    StringBuilder itemString = new StringBuilder();
    foreach(var item in items)
        itemString.AppendFormat(format, item.Key, item.Value);

    return itemString.ToString(); 
}
Gabe
This won't work as you think - `IDictionary<string, string>` is not compatible with `IDictionary<string, object>` - you could make the value generic however.
Lee
Lee: How about now?
Gabe
@Gabe: This will work without problems
abatishchev
@Gabe - That looks fine, +1 for using `IEnumerable<T>`.
Lee
+4  A: 
public string DictToString<TKey, TValue>(Dictionary<TKey, TValue> items, string format)
{
    format = String.IsNullOrEmpty(format) ? "{0}='{1}' " : format;
    return items.Aggregate(new StringBuilder(), (sb, kvp) => sb.AppendFormat(format, kvp.Key, kvp.Value)).ToString();
}
Lee
nice and concise.
Atømix
I think you can use `IDictionary`
abatishchev
A: 

Gabe, if you are going to be generic, be generic:

public string DictToString<T>(IDictionary<string, T> items, string format) 
{ 
    format = String.IsNullOrEmpty(format) ? "{0}='{1}' " : format;  

    StringBuilder itemString = new StringBuilder(); 
    foreach(var item in items) 
        itemString.AppendFormat(format, item.Key, item.Value); 

    return itemString.ToString();  
} 
James Curran
I originally meant generic in the "uses IDictionary instead of Dictionary" sense, but I was already doing what you suggested as you posted yours.
Gabe
@Gabe: The thing is I don't think you can pass a `Dictionary<string, string>` as a `IDictionary<string,object>`
James Curran
You're right; I just posted before I was really done writing the damn thing.
Gabe
@Gabe: The hazards of FGITW.
James Curran
@Gabe, @James: `StringBuilder` has next overload: `AppendFormat(string, object, object)` so you can use both generic - key and value
abatishchev
@abatishchev; I wasn't worried about StringBuilder. Gabe had changed the signature of the function.
James Curran
+1  A: 

I think efficiency is hardly a concern with only 10 strings, but maybe you don't want to rely on it only being ten.

Concatenation of Strings creates a new String object in memory, since String objects are immutable. This also suggest other String operations may create new instances, like replace. Usually this is avoided by using StringBuilder.

StringBuilder avoids this by using a buffer which it operates on; when the value of the StringBuilder is concatenated with another String the contents are added to the end of the buffer.

However there are caveats, see this paragraph:

Performance considerations

[...]

The performance of a concatenation operation for a String or StringBuilder object depends on how often a memory allocation occurs. A String concatenation operation always allocates memory, whereas a StringBuilder concatenation operation only allocates memory if the StringBuilder object buffer is too small to accommodate the new data. Consequently, the String class is preferable for a concatenation operation if a fixed number of String objects are concatenated. In that case, the individual concatenation operations might even be combined into a single operation by the compiler. A StringBuilder object is preferable for a concatenation operation if an arbitrary number of strings are concatenated; for example, if a loop concatenates a random number of strings of user input.

So a (contrived) case like this should probably not be replaced with StringBuilder:

string addressLine0 = Person.Street.Name +  " " + Person.Street.Number + " Floor " + Person.Street.Floor;

...as the compiler might be able to reduce this to a more efficient form. It is also highly debatable if it would inefficient enough to matter in the greater scheme of things.

Following Microsoft's recommendations you probably want to use StringBuilder instead (like the other highly adequate answers show.)

Skurmedel
Nitpick: The fact that string concatenation using the `+` operator creates new `string` objects in memory is actually unrelated to the fact that the `string` type is immutable (though both are true).
Dan Tao
@Dan Tao: I'm a bit thick at the moment, could you elaborate on why? It seems String + String operations get special treatment from the compiler, does this have anything to do with it?
Skurmedel
@Skurmedal: So, the `+` operator is static, takes two arguments, and returns a new value. You could think of it, really, as a static method. Now, just because a method accepts two arguments and returns a new value does not imply that the type of the arguments is immutable. I could `Concat` two `List<T>` objects together to get a new `IEnumerable<T>` without affecting either `List<T>`; this wouldn't be because `List<T>` is immutable (it's not). It would just be the way `Concat` works. So even though `string` is immutable, it could just as easily *not* be -- as far as concatenation is concerned.
Dan Tao
@Skurmedal: To put it another way: any time I write `x = y + z;` I am assigning a new value to the variable `x`. This is unrelated to whether `x`'s type is immutable. Immutable type or not, *assigning a new value* is *always* going to change what's stored in a variable. So `x = y + z;` is going to change what's at `x`, no question. What's really *weird* to think about is that if the type were *mutable*, then you could conceivably write the statement `x = y + z;` *and actually change the value of `y` or `z`* (or both!) -- if the `+` operator for that type were defined to do so.
Dan Tao
+2  A: 

This method

public static string ToFormattedString<TKey, TValue>(this IDictionary<TKey, TValue> dic, string format, string separator)
{
    return String.Join(
        !String.IsNullOrEmpty(separator) ? separator : " ",
        dic.Select(p => String.Format(
            !String.IsNullOrEmpty(format) ? format : "{0}='{1}'",
            p.Key, p.Value)));
}

used next way:

dic.ToFormattedString(null, null); // default format and separator

will convert

new Dictionary<string, string>
{
    { "a", "1" },
    { "b", "2" }
};

to

a='1' b='2'

or

dic.ToFormattedString("{0}={1}", ", ")

to

a=1, b=2

Don't forget an overload:

public static string ToFormattedString<TKey, TValue>(this IDictionary<TKey, TValue> dic)
{
    return dic.ToFormattedString(null, null);
}

You can use generic TKey/TValue because any object has ToString() which will be used by String.Format().

And as far as IDictionary<TKey, TValue> is IEnumerable<KeyValuePair<TKey, TValue>> you can use any. I prefer IDictionary for more code expressiveness.

abatishchev
+1 for static and whoa... one line. Hard to read but definitely concise
Atømix