API Design: The why of "StringBuilder.AppendFormat()" design


“Why does StrignBuilder.AppendFormat() need anything more than AppendFormat(string, object[])?” — Curious StringBuilder.AppendForamt user


This is a great question. Before I begin, let’s visit what overloads AppendFormat has….

AppendFormat( string, object);
AppendFormat( string, object, object);
AppendFormat( string, object, object, object);

AppendFormat( string, object[]);
AppendFormat (IFormatProvider, string, object[])


An obvious question is, why do we need the first three (the ones in blue)? (Obviously, we need the one with IFormatProvider…)


Here is what I learnt:


1) Some languages doesn’t support “params” syntax. By having those overloads, we allow a short cut that allow the developer to directly use it without having to create an array.


2) Allows the option for the CLR engine to do performance optimization (without array creation or other optomization) on common scenarios. (However, the CLR engine has not taken up on this option…)


<editorial comment>
In the past 5 months on the BCL team, I have learnt alot about design decisions made on the current BCL APIs and some of its consequences. Many times, the lessons of poor design does not show up until you have ship multiple version of the APIs. I think I am gonna start sharing some of the stuff I picked up from the team on this blog. Since V1, the BCL team has lots of experiences to share. A lot of successes, and some failures ;). (By the way, if you don’t know already, Krzysztof and Brad did a great job of sharing some of the successes and failures in his Framework Design Guidelines book. It’s a great read! The BCL team is the Guidelines’ first customer, and I find VERY intersting to learn how the BCL APIs relates or drive these guidelines.) Anyways, my first post on this topic is not really a “lessons learnt”, it is more a “I always wonder why….”.
</editorial comment>


If you have any “I always wonder why the BCL did <Some API> this way…”, please send it to me. I’d love to find out and share with you.


 


 

Comments (7)

  1. Shahar says:

    <<<

    Allows the option for the CLR engine to do performance optimization (without array creation) on common scenarios. (However, the CLR engine has not taken up on this option…)

    >>>

    Is this really an optimization the "CLR engine" needs to do? It seems more like an optimization the compiler does when it generates the IL.

    Furthermore, the C# compiler seems to do the right thing and not create an array – in the following sample, "No array" gets output to the console.

    class Program

    {

       static void f(object[] o)

       {

           Console.WriteLine("yes array");

       }

       static void f(object o)

       {

           Console.WriteLine("No array");

       }

       static void Main(string[] args)

       {

           f(1);

       }

    }

  2. KathyKam says:

    I should be more explicit… creating those overload is an opportunity for us to optimize it. Not creating array is one way, but we can do other performance optomizations.

  3. Random Reader says:

    Shahar: you left off the "params" keyword in your sample.

    But you’re right; both the C# and C++ compilers favor flat parameters over arrays marked with ParamArrayAttribute as part of their normal overload resolution process.  They generate nearly identical IL for the same code.

  4. KathyKam says:

    Yes, the C# and C++ compilers favor the flat parameters, but with out those overload, you’ll always have to create an array. By the way, the CLR engine actually creates an array internally to handle those overloads. Theoretically, one performance optimization we can do is to not create an underlying array.  

  5. I am wondering why some methods in BCL as String.Format and StringBuilder.AppendFormat were designed to accept their substituting values parameters as parameters of type object or array of objects (or param array) as in AppendFormat(string, object) and AppendFormat(string, object[]). Why were these methods not designed to accept strings instead of objects? I think accepting those parameters as strings – when possible – would be better as long as the parameter on which to apply formatting is a string and as long as every class in .NET inherits System.Object. This way, the calling client for this method has to pass strings and by uniformly calling object.ToString() – or another custom method maybe – when passing the substituting values. This way; boxing would be avoided when passing value types to the AppendFormat method! Personally, I keep calling the .ToString method explicitly to pass that function a string reference which deos not cause boxing as System.String as a reference type.

  6. buzz44 says:

    Hey there,

    I just started using the StringBuilder class and AppendFormat method recently. What is the point of having the AppendFormat (format as String, ParamArray args() as Object) overloaded member?

    A ParamArray lets you have an unlimited number of parameters, but to format it you *need to know* what parametres, i.e. what index.

    I can do this…

    StringBuilder.AppendFormat("{0} {1}", Parameters)

    … but it doesn’t know what is the last object in the ParamArray. I want to be able to format EACH object in the ParamArray. Are there a format syntax I have not found yet or do I have to format each object individually in a For loop so I can loop through each object in the ParamArray and stop on the last one.

    Hope that makes sense,

    buzz.