3

If you go right now and type string.Format into your IDE, you'll see that there are 4 different overloads: one taking a string and object, another taking a string and two objects, then one taking three objects, and finally one that uses params. According to this answer, this is because params generates 'overhead', and some other languages may not support it.

My question is, why can't a method call like this:

void Foo()
{
    Bar(1, 2, 3);
}

void Bar(params int[] args)
{
    // use args...
}

Be essentially transformed at compile time to

void Foo()
{
    Bar(new[] { 1, 2, 3 });
}

void Bar(int[] args)
{
    // use args...
}

? Then it wouldn't create any overhead except for the array creation (which was necessary anyway), and would be fully compatible with other languages.

The number of arguments is already known at compile-time, so what's preventing the C# compiler from doing some kind of string substitution and making the first scenario essentially syntactic sugar for the second? Why did we have to implement hidden language features specifically to support variadic arguments?

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • 2
    Array creation is the overhead linked answer talks about. You're not getting rid of that overhead. Providing overloads with one, two, three parametres does make the array allocation unnecessary when called with less than 4 items. – MarcinJuraszek Jul 19 '15 at 00:12
  • @MarcinJuraszek Well if you look at the question, it seems that the non-`params` overloads of `string.Format` just call the `params` one after creating a new array. – James Ko Jul 19 '15 at 00:14
  • Memory overhead is not the only problem here - creating an array and filling it with items generates additional IL in your method, which makes it less likely to be inlined by JIT. – MarcinJuraszek Jul 19 '15 at 00:15
  • This is just a guess, but if the compiler knows a method takes exactly n parameters instead of a variable number of parameters, it may be able to better optimize the code for performance, such as storing variables in registers instead of having to create an array. – Matthew Jul 19 '15 at 00:16
  • @Matthew and MarcinJuraszek, what's the point of optimizing/inlining the code if it just calls a method that does what you were trying to avoid- creating an array, filling it with 3 elements, and passing it to the `params` overload? – James Ko Jul 19 '15 at 00:19
  • @user2864740 I still can't see the point if avoiding the `params` overhead (which is the cost of creating an array and populating it with 3 elements) just calls another method that does exactly that and passes it to `params`. – James Ko Jul 19 '15 at 00:20
  • @user2864740 Then back to my question, why can't `params` just be syntactic sugar that is eliminated at compile time? Then another language could just create a regular array and pass it in. – James Ko Jul 19 '15 at 00:23
  • @user2864740: So essentially, you're saying that calling the method with non-varargs parameters is better because it postpones the array creation until runtime and therefore generates less IL? As MarcinJuraszek pointed out, overloads without params are more likely to be inlined by the JIT so either way looks like it will have the same results (excluding the extra null check performed in non-varargs overloads). | Also yes, I am aware there is overhead creating the new array, but how does this give `params int[] args` more overhead than `int[] args`? – James Ko Jul 19 '15 at 00:34
  • @user2864740 Are you sure that the latter case is not a case of `params` being slower in a way? I counted 15 instructions for `params`, and 6 for the regular case, which indicates that the compiler was somehow able to optimize out the latter case (the [documentation](https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.initializearray%28v=vs.110%29.aspx) for `RuntimeHelpers.InitializeArray` says it's a 'performant' way to initialize an array). – James Ko Jul 19 '15 at 02:55

1 Answers1

3

The title makes an incorrect assumption.

Both a params and a non-params methods take an array; the difference is the compiler will emit the IL to create an array implicitly when making a params method call. An array is passed to both methods, as a single argument.

This can be seen in this .NET Fiddle (view "Tidy Up -> View IL").

using System;

public class Program
{
    public static void Main()
    {
        var a1 = 1;
        var a2 = 2;
        var a3 = 3; 
        with_params(a1,a2,a3);
        no_params(new [] {a1,a2,a3});
    }

    public static void with_params(params int[] x) {}
    public static void no_params(int[] x) {}
}

In both cases the IL is identical; a new array is created, it is populated, and the array is supplied to the invoked method.

There is an "exception" to this identical IL generation in that the compiler can move out constant-valued arrays when used in the non-parameter form and use 'dup' initialization, as seen here. However, a new array is supplied as the argument in both cases.

user2864740
  • 60,010
  • 15
  • 145
  • 220
  • Thank you, this is a really good answer- but if `params int[]` and `int[]` are exactly the same, then why can't [some other languages](http://stackoverflow.com/questions/2796731/why-do-the-overloads-of-string-format-exist/2796763#2796763) call `params int[]`? – James Ko Jul 19 '15 at 02:57
  • @JamesKo It would be a current design limitation of the particular language itself. In these cases they can specify an array explicitly or, presumably, call one of the N-parameter overloads. – user2864740 Jul 19 '15 at 02:58
  • Yes, but if `params` was just a compile-time thing, then shouldn't they be able to pass in a plain array? – James Ko Jul 19 '15 at 02:59
  • @JamesKo Yes, they should be able to. But if the languages supports the overloads (and it really must to work with CLR/.NET) it can still get away with "works like varargs" for 1..4 parameters even if unaware of `params`-behavior. (I only use C# and VB.NET which support params, and I don't know of a language that *doesn't*.) – user2864740 Jul 19 '15 at 02:59