Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Will `params` in C# always cause a new array to be allocated on every call?

C#/.NET has variadic function parameters by passing an Array type by-reference (as opposed to C/C++ which just places all of the values directly on the stack, for better and for worse).

In the C# world this has a neat advantage of allowing you to call the same function with either 'raw' arguments or a reusable array instance:

CultureInfo c = CultureInfo.InvariantCulture;

String formatted0 = String.Format( c, "{0} {1} {2}", 1, 2, 3 );

Int32 third = 3;
String formatted0 = String.Format( c, "{0} {1} {2}", 1, 2, third );

Object[] values = new Object[] { 1, 2, 3 };
String formatted1 = String.Format( c, "{0} {1} {2}", values );

This means that the generated CIL is equivalent to:

String formatted0 = String.Format( c, "{0} {1} {2}", new Object[] { 1, 2, 3 } );

Int32 third = 3;
String formatted0 = String.Format( c, "{0} {1} {2}", new Object[] { 1, 2, third } );

Object[] values = new Object[] { 1, 2, 3 };
String formatted1 = String.Format( c, "{0} {1} {2}", values );

Which means that (in a non-optimizing JIT compiler) every call will allocate a new Object[] instance - though in the third example you're able to store the array as a field or other reusable value to eliminate the new allocation on every call to String.Format.

But in the official CLR runtime and JIT are any optimizations done to eliminate this allocation? Or perhaps is the array tagged specially so that it will be deallocated as soon as execution leaves the scope of the call-site?

Or, perhaps, because the C# or JIT compiler knows the number of arguments (when used "raw") could it do the same thing as the stackalloc keyword and place the array on the stack, and thus not need to deallocate it?

like image 473
Dai Avatar asked Jul 30 '16 22:07

Dai


Video Answer


1 Answers

Yes, a new array is allocated every time.

No, no optimizations are done. There is no "interning" of the kind you suggest. After all, how could there be? The receiving method can do anything with the array, including mutating its members, or reassigning array entries, or passing a reference to the array on to others (without params then).

No special "tagging" of the kind you suggest, exists. These arrays are garbage collected in the same way as anything else.


Addition: There is of course one special case where "interning" of the kind we discuss here, could be easy to do, and that is for length-zero arrays. The C# compiler could call Array.Empty<T>() (which returns the same length-zero array each time) instead of creating a new T[] { } whenever it encountered a call to params where a length-zero array is needed.

The reason for this possibility is that length-zero arrays are truly immutable.

Of course "interning" of length-zero arrays would be discoverable, for example the behavior of this class would change if the feature were to be introduced:

class ParamsArrayKeeper
{
  readonly HashSet<object[]> knownArrays = new HashSet<object[]>(); // reference-equals semantics

  public void NewCall(params object[] arr)
  {
    var isNew = knownArrays.Add(arr);
    Console.WriteLine("Was this params array seen before: " + !isNew);
    Console.WriteLine("Number of instances now kept: " + knownArrays.Count);
  }
}

Addition: Given that the "strange" array covariance of .NET does not apply to value types, are you sure your code:

Int32[] values = new Int32[ 1, 2, 3 ];
String formatted1 = String.Format( CultureInfo.InvariantCulture, "{0} {1} {2}", values );

works as intended (if the syntax is corrected to new[] { 1, 2, 3, } or similar, this will go to the wrong overload of String.Format, for sure).

like image 119
Jeppe Stig Nielsen Avatar answered Sep 18 '22 12:09

Jeppe Stig Nielsen