I was browsing around some MSDN materials in VB.Net and came across this puzzling claim "
How to: Force an Argument to Be Passed by Value".
The obvious question that sprang to my mind was: Is this real? I know nothing in CLI that supports this kind of thing. There is also confusing statement in that reference
If a parameter is declared ByRef, Visual Basic expects to pass the corresponding argument by reference. This allows the procedure to change the value of the programming element underlying the argument in the calling code.
If the "
value of the programming element" means the object reference or memory address, then that statement is true.
In .Net if the object being passed across is a reference object, no matter you pass use
ByVal (in C# no qualifier needed) or
ByRef (in C# with the ref qualifier), the called function can change
the state of the object but only the
reference or the memory address of the object can be modified if using
ByRef.
So what has VB.Net invented that CLI does not support? Obviously this must be a source level construct and not IL construct.
It turns out that there is really nothing new or to trumpet about.According to the
documentation, if you surround the argument, which is specified by the called function to have
ByRef convention, with a bracket,
you can prevent the called function from modifying the object address, like this:
Dim a As DemoClass
Dim b As DemoClass
a = New DemoClass(10, "Jack")
b = a
Show(a, "Before")
SomeFunctions.DoSomethingA((a)) ' L1
Show(a, "After")
Debug.Print("Are the same ? " & (Object.ReferenceEquals(a, b).ToString()))
Line L1 shows the protection syntax. The Debug.Print shows object
a and
b are occupying the same memory location. So the
(a) syntax defeats the function's parameter's convention which is specified as:
public static class SomeFunctions {
public static void DoSomethingA( ref DemoClass a ) {
a = new DemoClass( a.Number + 100, "Bye " + a.Name );
}
}
So how can VB.Net defeat the convention specified by a C# function?
To answer this question you need to examine the IL code generated by VB to see if it is merely a trick. This is the IL code of the above VB.Net fragment:
.locals init ([0] class [MySupport]MySupport.DemoClass a,
[1] class [MySupport]MySupport.DemoClass b,
// .....
[4] class [MySupport]MySupport.DemoClass VB$t_ref$S0,
[5] bool VB$t_bool$S0)
IL_0000: nop
IL_0001: ldc.i4.s 10
IL_0003: ldstr "Jack"
IL_0008: newobj instance void [MySupport]MySupport.DemoClass::.ctor(int32,
string)
IL_000d: stloc.0
IL_000e: ldloc.0
IL_000f: stloc.1
IL_0010: ldloc.0
IL_0011: ldstr "Before"
IL_0016: call void TestParameterPassing.Module1::Show(class [MySupport]MySupport.DemoClass,
string)
IL_001b: nop
IL_001c: ldloc.0
IL_001d: stloc.s VB$t_ref$S0
IL_001f: ldloca.s VB$t_ref$S0
IL_0021: call void [MySupport]MySupport.SomeFunctions::DoSomethingA(class [MySupport]MySupport.DemoClass&)
IL_0026: nop
IL_0027: ldloc.0
IL_0028: ldstr "After"
IL_002d: call void TestParameterPassing.Module1::Show(class [MySupport]MySupport.DemoClass,
string)
IL_0032: nop
IL_0033: ldstr "Are the same \? "
IL_0038: ldloc.0
IL_0039: ldloc.1
IL_003a: call bool [mscorlib]System.Object::ReferenceEquals(object,
object)
IL_003f: stloc.s VB$t_bool$S0
IL_0041: ldloca.s VB$t_bool$S0
IL_0043: call instance string [mscorlib]System.Boolean::ToString()
IL_0048: call string [mscorlib]System.String::Concat(string,
string)
IL_004d: call void [System]System.Diagnostics.Debug::Print(string)
Notice the local variable 4,
VB$t_ref$S0, a compiler generated auto-variable of the same type as the function's parameter. From line
IL_001c to
IL_001f, the IL code is to stash the object in
a to
VB$t_ref$S0 and then pass this temporary variable by reference to the called function. The idea is to let that function modifies a sacrificial object without harming the protected one inside the bracket, that is
a.
You can achieve the same result in C# as follows:
DemoClass a = new DemoClass( 23, "Peter" );
DemoClass b = a;
DemoClass tmp = a;
SomeFunctions.DoSomethingA( ref tmp );
Debug.WriteLine( String.Format( "Are they the same? " + Object.ReferenceEquals( b, a ) ) );
The variable
tmp is the throw away one.
So there is nothing new. It is just a simple convenience trick - nothing more and nothing less.