Optional Paramters- What really occurs?

  • Thread starter Thread starter William Ryan
  • Start date Start date
W

William Ryan

I just picked up a copy of John Robbins' debugging book and started to look
at disassembled code. Anyway, I hate optional Parameters in VB, but I was
checking them out to see what IL is created. I've done this before with
Modules, and saw that <gasp> they behave just like sealed classes with only
static members.

Anyway, it looks like Optional Parameters are nothing but a way to tell the
compiler to write some overloads for you. So, in the case of a Subroutine I
was looking at that had 15 optional parameters (and no, I didn't write it)
it looked like the IL was showing 15 overloaded methods. I'm by no means an
Expert, but if this is the case...am I right in that it's kind of scarry?
I mentioned this to the guy who wrote it and he said I'm off of my rocker.
According to him, this is one of the benefits of VB.NET over C# in that you
don't have to use extensive overloading and it's more efficient. From the
IL though, this looks like the opposite.

So without stirring a VB.NET vs. C# debate, am I write about this or am I
reading it wrong?

Thanks,

Bill
 
William Ryan said:
I just picked up a copy of John Robbins' debugging book and started to look
at disassembled code. Anyway, I hate optional Parameters in VB, but I was
checking them out to see what IL is created. I've done this before with
Modules, and saw that <gasp> they behave just like sealed classes with only
static members.

Anyway, it looks like Optional Parameters are nothing but a way to tell the
compiler to write some overloads for you. So, in the case of a Subroutine I
was looking at that had 15 optional parameters (and no, I didn't write it)
it looked like the IL was showing 15 overloaded methods. I'm by no means an
Expert, but if this is the case...am I right in that it's kind of scarry?
I mentioned this to the guy who wrote it and he said I'm off of my rocker.
According to him, this is one of the benefits of VB.NET over C# in that you
don't have to use extensive overloading and it's more efficient. From the
IL though, this looks like the opposite.
From what I'm seeing from ildasm, the optional keyword doesn't provide
overloads (atleast not in this compiler), but instead inserts optional value
values directly into code.
I wrote this little program to play with optionals:
Module Module1
Sub Main()
Dim X As Class1 = New Class1
X.Cat(, "meow")
X.Dog()
End Sub
End Module
Class Class1
Public Function Cat(Optional ByVal S As String = "neko", Optional ByVal
sound As String = "Nyao") As Boolean
Console.WriteLine(S + sound)
End Function
Public Function Dog(Optional ByVal S As String = "inu") As Boolean
Console.WriteLine(S)
End Function
End Class

I then opened it in ildasm, it showed one instance each of Dog and Cat, and
the disassembled call line for X.Cat(,"Meow) was:
IL_0008: ldstr "neko"
IL_000d: ldstr "meow"
IL_0012: callvirt instance bool VBOptionalTest.Class1::Cat(string,
string)

for X.Dog() it was:
IL_0019: ldstr "inu"
IL_001e: callvirt instance bool VBOptionalTest.Class1::Dog(string)

the optional keyword appears to directly embed the values into the calling
code, making optional arguments value changes a binary breaking change.

I had heard this before, but this is the first time i've actually verified
it.
 
William Ryan said:
I just picked up a copy of John Robbins' debugging book and started to look
at disassembled code. Anyway, I hate optional Parameters in VB, but I was
checking them out to see what IL is created. I've done this before with
Modules, and saw that <gasp> they behave just like sealed classes with only
static members.

Anyway, it looks like Optional Parameters are nothing but a way to tell the
compiler to write some overloads for you. So, in the case of a Subroutine
I

Forgot to mention, I think it would have been better if the compiler did
generate overloads automatically instead of implicitly forcing the calling
code to embed the values, but I didn't design the langauge.
Can't say I'd be opposed to a C# optional parameter system that is simply a
shortcut to manually writing lots of overloads, assuming it was written
within bounds, but I wouldn't be particulary thrilled with the VB method.
Not to extol one language over the other, I just don't like this particular
design in any language, never have.
 
Daniel:

I totally appreciate the post. I don't have the IDE in front of me but
here's what I remember off of the top of my head. In your example for cat,
if you pass in "Neko" for S and nothing for sound, s:="Neko" or pass in
sound but not S, let alone make three calls, one with both params, one with
the first param but not the second, and one with the second param but not
the first, It looked like there were three different calls. I wish I had
the disassembly in front of me (and probably should quit posting until I do)
but it just seemed really weird b/c it looked like each of the cases was
being written differently. Let me check back when I get to wrok.

Thanks Again,

Bill
 
As a further test, move Class1 into a dll. Leave your test "module" in a
separate .exe. Compile everything, copy the exe and dll to a new folder and
run it to prove it works. Then change the optional parameters in the dll
and copy this new dll over to the folder listed above. Do not move the .exe
(make sure you use the original one). If you run it, you'll find out that
you do not get the new defaults. That is, the default values are really
plugged into calling code (as shown in previous post). If you don't
recompile the calling code, it won't pick up the new defaults. Sometimes
this could be the desired behavior, but generally (IMO) if you are creating
new defaults, it's because you really want the default behavior to change.
With optional parameters you have to recompile everything, which may or may
not be a big undertaking (depends on if multiple vendors, etc).

To my understanding, this is why optional parameters were not included in
c#.
 
William Ryan said:
Daniel:

I totally appreciate the post. I don't have the IDE in front of me but
here's what I remember off of the top of my head. In your example for cat,
if you pass in "Neko" for S and nothing for sound, s:="Neko" or pass in
sound but not S, let alone make three calls, one with both params, one with
the first param but not the second, and one with the second param but not
the first, It looked like there were three different calls. I wish I had
the disassembly in front of me (and probably should quit posting until I do)
but it just seemed really weird b/c it looked like each of the cases was
being written differently. Let me check back when I get to wrok.
Hrmm, I'm curious about what you found, I added some extra calls (calling
Cat with both params, one of each, and no params all together) and the
resultant IL was:
IL_0008: ldstr "neko"
IL_000d: ldstr "Nyao"
IL_0012: callvirt instance bool VBOptionalTest.Class1::Cat(string,
string)
IL_0017: pop
IL_0018: ldloc.0
IL_0019: ldstr "neko"
IL_001e: ldstr "meow"
IL_0023: callvirt instance bool VBOptionalTest.Class1::Cat(string,
string)
IL_0028: pop
IL_0029: ldloc.0
IL_002a: ldstr "Cat"
IL_002f: ldstr "Nyao"
IL_0034: callvirt instance bool VBOptionalTest.Class1::Cat(string,
string)
IL_0039: pop
IL_003a: ldloc.0
IL_003b: ldstr "Cat"
IL_0040: ldstr "Meow"
IL_0045: callvirt instance bool VBOptionalTest.Class1::Cat(string,
string)

Still showing the call to the same method with the same pattern. It is
curious, to say the least. I'm looking forward to seeing what it was you
found.
 
Daniel,
Check out the IL function definition itself. The parameters in the IL have
the [opt] attribute on them.

..method public static bool Cat([opt] string S,
[opt] string sound) cil managed
{
.param [1] = "neko"
.param [2] = "Nyao"
// Code size 16 (0x10)
.maxstack 2
.locals init ([0] bool Cat)

Hope this helps
Jay
 
William,
I mentioned this to the guy who wrote it and he said I'm off of my rocker.
According to him, this is one of the benefits of VB.NET over C# in that you
don't have to use extensive overloading and it's more efficient. From the
I would think overloading would be more efficient! (depending on are you
defining efficiency to be code size or speed).

If I have a function that has 15 optional parameters, and I call that
function with 1 parameter 50 times. All 50 times all 15 parameters need to
be pushed on the stack, which causes my assembly to be larger. Where as with
overloading all 50 calls would push 1 parameter, which would be smaller
code. Now most of my functions with overloaded parameters would only have 1
to 3 parameters that they are overloaded on, which I would suspect would
cause those functions to be in-lined by the JIT. Hence I would expect that
with overloaded I get smaller assemblies, plus the 'same' efficiency as the
optionals...

If you actually have a function with 15 overloaded parameters I would
suggest the Introduce Parameter Object Refactoring:
http://www.refactoring.com/catalog/introduceParameterObject.html

Just a thought
Jay
 
Jay:

I don't use the things, but a co-worker and friend of mine uses them
extensively. As a matter of fact, I think some of those functions he has
use more like 20-30 optionals. I'm going the check out your article
reference right now...thanks. In these regards, I usually just pass in one
object or structure that has the values I want, but different people code
differently.

Thanks again

Billl
 
William,
I don't use the things, but a co-worker and friend of mine uses them
extensively.
In VB6 I used optional parameters all the time, in VB.NET I favor Overloaded
parameters. For the reasons I listed earlier.
In these regards, I usually just pass in one
object or structure that has the values I want,
You already understand the Introduce Parameter Object ;-)

Jay
 
Jay B. Harlow said:
William,
I would think overloading would be more efficient! (depending on are you
defining efficiency to be code size or speed).

If I have a function that has 15 optional parameters, and I call that
function with 1 parameter 50 times. All 50 times all 15 parameters need to
be pushed on the stack, which causes my assembly to be larger. Where as with
overloading all 50 calls would push 1 parameter, which would be smaller
code.

That really depends on how the overloading is done. With manual
overloading, the overloaded method with fewer parameters usually just
calls the overloaded method with all the parameters. That means you
still need 15 parameters to be pushed on the stack, but before then you
need the 1 parameter to be pushed onto the stack first. In effect, you
end up with another level of indirection to start with, but I don't
think you save anything, unless the overloaded version with fewer
parameters actually contains a complete copy of the code, so doesn't
need to call the version with all the parameters. Here's a concrete
example of what I mean:

[Overloading by indirection]
public void Foo (int x)
{
Console.WriteLine (x);
}

public void Foo ()
{
Foo (0);
}

[Overloading by copying]
public void Foo (int x)
{
Console.WriteLine (x);
}

public void Foo()
{
int x = 0;
Console.WriteLine (x);
}


The second way doesn't involve as much indirection, but you'll end up
with more code to be JITted (although it may be possible to JIT it more
tightly).
 
Jon,
We're saying the same thing. I may have said it in a round about way. ;-)

Remember I am talking code size, not necessarily execution speed.

I was considering overloading by indirection not overloading by copying.

Using overloading by indirection if I have:
public void Foo (int x)

public void Foo ()

If I call "Foo()" fifty times, I will have 51 call instructions. 50 of them
will not push any parameters, then an extra call to "Foo(int)", so at
execution time I will actually execute 100 calls. I suspect "Foo()" itself
will be inlined (not guaranteed) so I may have only 50 calls 'executed'.
However for code size 53 'instructions' would be generated: 50 for the call
to "Foo", 3 inside of "Foo" (the call to "Foo(int)" and the return).

However if I had an optional parameter on "Foo(int)" (as in VB.NET), I would
only have 50 calls to a single function, and all 50 of them would push a
parameter, for a code size of 100 'instructions' (a push & a call 50 times).

Now consider what I was originally trying to state: If I have a function
that has 15 optional parameters and I call that function with 1 parameter 50
times.

With VB.NET's optional parameter:
50 calls + 50 * 15 parameters = 750 "instructions"

With overloading by indirection:
50 calls + 15 parameters + 15 parameters + 2 overloaded function = 82
"instructions"

The first 15 parameters is calling foo(int) the second 15 parameters is
inside of foo(int), I'm assuming I did not chain all 15 versions of Foo,
that each version calls the 15 parameter version.

In the above "instruction" is loosely referring to an IL instruction.

Can you tell I use to program in x86 assembler and 370 assembler?

Does that make sense? (too many overloaded uses of the word call in my
description ;-)

Jay

Jon Skeet said:
Jay B. Harlow said:
William, that
you the
I would think overloading would be more efficient! (depending on are you
defining efficiency to be code size or speed).

If I have a function that has 15 optional parameters, and I call that
function with 1 parameter 50 times. All 50 times all 15 parameters need to
be pushed on the stack, which causes my assembly to be larger. Where as with
overloading all 50 calls would push 1 parameter, which would be smaller
code.

That really depends on how the overloading is done. With manual
overloading, the overloaded method with fewer parameters usually just
calls the overloaded method with all the parameters. That means you
still need 15 parameters to be pushed on the stack, but before then you
need the 1 parameter to be pushed onto the stack first. In effect, you
end up with another level of indirection to start with, but I don't
think you save anything, unless the overloaded version with fewer
parameters actually contains a complete copy of the code, so doesn't
need to call the version with all the parameters. Here's a concrete
example of what I mean:

[Overloading by indirection]
public void Foo (int x)
{
Console.WriteLine (x);
}

public void Foo ()
{
Foo (0);
}

[Overloading by copying]
public void Foo (int x)
{
Console.WriteLine (x);
}

public void Foo()
{
int x = 0;
Console.WriteLine (x);
}


The second way doesn't involve as much indirection, but you'll end up
with more code to be JITted (although it may be possible to JIT it more
tightly).
 
Does that make sense? (too many overloaded uses of the word call in my
description ;-)

Yup, that made perfect sense - I just didn't get what you were trying
to say in your first post.
 
Doh!
I think my math is off here:
With overloading by indirection:
50 calls + 15 parameters + 15 parameters + 2 overloaded function = 82
"instructions"
should be:
50 calls + 50 parameters + 15 parameters + 2 overloaded function = 117

As the 50 calls each have a parameter, then the overloaded function has 15
parameters...

Either way one gets the idea...

Jay

Jay B. Harlow said:
Jon,
We're saying the same thing. I may have said it in a round about way. ;-)

Remember I am talking code size, not necessarily execution speed.
With VB.NET's optional parameter:
50 calls + 50 * 15 parameters = 750 "instructions"

With overloading by indirection:
50 calls + 15 parameters + 15 parameters + 2 overloaded function = 82
"instructions"

The first 15 parameters is calling foo(int) the second 15 parameters is
inside of foo(int), I'm assuming I did not chain all 15 versions of Foo,
that each version calls the 15 parameter version.

In the above "instruction" is loosely referring to an IL instruction.

Can you tell I use to program in x86 assembler and 370 assembler?

Does that make sense? (too many overloaded uses of the word call in my
description ;-)

Jay
<<snip>>
 
Back
Top