A Bit of History
From the past 2 days, I have been taking a few interviews (telecons mostly) for guys having 4-6 years experience in C# .NET though I would not claim myself that I am much more talented than them in assessing them.
So on the same, for 4 guys I asked a few questions on params. The question I first asked was: What's the use of params in C#? For which the candidates gave away a straight bookish answer (yes, you can make out by the way they explain), but I did not stop there. I went a bit further and asked them (because they are more experienced and they should know more than what the book says): Why do we need params: For which they still answered right again as per all books/MSDN. Further I asked again: Why do I need it, I could have done just by passing an array with different size as argument. For which I did not get any answer, they seemeed to have got confused.
Then further to this, I again asked: Do you find any hidden problem with this feature or otherwise tell me how it works. For which again no answers. Yes you may think I am interviewing the wrong talented guys? May be, but I have to do it because of the protocol.
Description
So after these interviews, when I got back to my seat, even I got a bit curious to dig deeper into this topic. Although I knew already that every time you call a params method, it would build an array and creating this array is a bit of a performance overhead. But I never cared enough to convince myself as to what's been told is really what it is and to know more.
So this post is about that, upon disassembling the below code, I got below IL:
void ParamsMethod(params int[] arguments)
{
}
ParamsMethod(1,2,3)
IL_0001: ldc.i4.3
IL_0002: newarr [mscorlib]System.Int32
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldc.i4.0
IL_000a: ldc.i4.1
IL_000b: stelem.i4
IL_000c: ldloc.1
IL_000d: ldc.i4.1
IL_000e: ldc.i4.2
IL_000f: stelem.i4
IL_0010: ldloc.1
IL_0011: ldc.i4.2
IL_0012: ldc.i4.3
IL_0013: stelem.i4
IL_0014: ldloc.1
IL_0015: call void ConsoleApplication.Program::SomeMethod(int32[])
IL_001a: nop
As you can see, first the compiler has converted the call to a sequence of OpCode starting with creating an array via newarr opcode. Now I started to dig a bit on this keyword. As per that, arrays do get created on heap and the size is known by the number of arguments passed which is what the first IL opcode IL_0001: ldc.i4.3 talks about. The 3 at the end is the size of the array which needs to be created on heap. Do vary the arguments number, and see the IL code, the 3 changes to the appropriate number of arguments. With that, more IL instructions do get added for the additional arguments to be loaded onto array.
As soon as I read this, I started wondering since a valuetype
is getting created on heap do the values also gets boxed because they need to be created on heap. Based on some Googling, I came to know that array contents will not be boxed even though they get created on heap. The reason why it gets created on heap is because the array sizes are never known and internally the run time decides as per its rules that arrays are usually a long-lived objects. Hence heap is preferred.
Still I had a doubt why boxing is not required if a value type is getting stored on heap. It’s because the value type which is getting stored inside the array content is actually stored as value itself even though it’s on heap, unless you wish to reference that same array content value as reference type then boxing will happen. So in simple words, just because you need to store some thing on heap, does not mean you need to box it. If you box a valuetype because you wanted it to be a ref type, then it must be stored on the heap only.
So what I can infer from the above analysis is that: Every call creates a new array over heap.
Hence based on the above points, we need to carefully choose if we really need method having params in its signature, so that frequent usage of the same does not add performance overhead to the application.
Now coming back to my tricky question like “You can use direct array to pass instead of params”. The answer is really simple if you are still looking for it. Once array is declared and defined with contents, you cannot modify it again as shown below:
int[] myarray = { 1, 2, 3 };
myarray = {1,2,3,4,5};
So every time you need to pass varying number of arguments to the method taking int[]
array as argument, then you need to create new
as shown below:
int[] myarray = { 1, 2, 3 };
myarray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };
The above style is not a good coding and not really worth either to write. So this way params do come in handy for a nice interface.
I tried doing a performance study between params and passing array, since both ways the run time is creating arrays on heap, just look at the code:
static void ParamsMethod(params int[] args)
{
var x = args.Length;
}
static void NonParamMethod(int[] args)
{
var x = args.Length;
}
static void Main(string[] args)
{
int count = 1000;
Stopwatch sp = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
ParamsMethod(1, 2, 3, 4, 5, 6);
}
sp.Stop();
Console.WriteLine(sp.ElapsedTicks);
int[] myArray = { 1, 2, 3, 4, 5, 6 };
sp = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
NonParamMethod(myArray);
}
sp.Stop();
Console.WriteLine(sp.ElapsedTicks); }
The output I got was:
465939
235953
Note: I did run the above test code multiple times to make sure that the output results are quite consistent. And yet it was. As well as kept the method body same and simple to have some consistency in results.
As you can see the difference is quite a lot, this is because in params style every call creates a new array on heap and in the later style only once created array is used for each call.
Hence based on all the above points, it is suggested to carefully decide whether to use params and the best guidelines suggest not to use params for critical methods in your application.
Thanks.
P.S: Your valuable comments/votes are much appreciated.
Filed under:
c#,
CodeProject,
dotnet Tagged:
.NET,
blog,
c#,
codeproject,
dotnet,
tips