18

I was using Reflector to look at the implementation of String.Format and had always been under the impression that the overloads of String.Format that took 1, 2 & 3 arguments were optimized versions of the method that takes an object array. However, what I found was that internally they create an object array and then call a method that takes an object array.

1 arg

public static string Format(string format, object arg0)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0 });
}

2 args

public static string Format(string format, object arg0, object arg1)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0, arg1 });
}

3 args

public static string Format(string format, object arg0, object arg1, object arg2)
{
    if (format == null)
    {
        throw new ArgumentNullException("format");
    }
    return Format(null, format, new object[] { arg0, arg1, arg2 });
}

Object array

public static string Format(string format, params object[] args)
{
    if ((format == null) || (args == null))
    {
        throw new ArgumentNullException((format == null) ? "format" : "args");
    }
    return Format(null, format, args);
}

Internally they all end up using the same code and so using the 1, 2 & 3 argument versions are no faster than the object array version.

So my question is - why do they exist?

When you use the object array version with a comma separated list of values, the compiler automatically converts the arguments into an object array because of the params/ParamArray keyword which is essentially what the 1, 2 & 3 versions do, so they seem redundant. Why did the BCL designers add these overloads?

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
John Mills
  • 10,020
  • 12
  • 74
  • 121

2 Answers2

8

One reason, as Hans mentions, is that creating an array is a lot of unnecessary overhead in most common cases of formatting a string. This saves space in the EXE.

Another reason is that not all languages support variadic functions (use of params in C#). This allows users of those languages to avoid array creation for the most common cases of string formatting. This saves a lot for languages that don't have simple syntax for array creation and initialization.

Gabe
  • 84,912
  • 12
  • 139
  • 238
  • "creating an array is a lot of unnecessary overhead" The array is created inside the method how is that any different in terms of "overhead"? – Chris Marisic Feb 26 '15 at 18:57
  • 2
    @ChrisMarisic: It's not the memory use or GC overhead of creating the arrays that's the problem; it's the extra opcodes. Calling `string.Format` with just a single string replacement requires only 3 IL opcodes, whereas it would require 10 opcodes if the overload didn't exist. Each additional parameter requires only 1 more opcode if it can use an overload, versus 4 more opcodes if it uses an array. Since a method cannot be inlined if it is too many opcodes, this has major implications. – Gabe Feb 26 '15 at 21:27
  • So the overloads are to increase the probability that Format call itself is inlined where it is used? Will it never be inlined if it has to use the params overload? – Chris Marisic Feb 26 '15 at 21:31
  • The overloads increase the probability that the method *containing* the `Format` call will be inlined. – Gabe Mar 15 '15 at 00:51
3

You are forgetting about the code in the app required to make the call. Creating the array and filling it takes a lot more IL than just passing 3 args.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • You're absolutely right. I'd not considered that. I guess you could say that those methods optimize calling code size rather than performance which I'd always thought they were there for. – John Mills May 09 '10 at 06:48
  • So the overloads make sense if you're calling it in direct IL? – Chris Marisic Feb 26 '15 at 18:58