Is it a waste of time to call a function in the foreach definition? - vb.net

Sometimes I have to run a loop where the list or array comes from a function.
I usually do it this way :
Dim list = SomeClass.GetMyList()
For Each item in list
'Do some stuff
Next
Is it the same as :
For Each item in SomeClass.GetMyList()
'Do some stuff
Next
I usually do the first way because I think that second way makes a call everytime it starts the next iteration, therefore wasting some time.
Am I right to think that way ? Or can I go ahead with second way as the compiler is smart enough not to make a call every round ?

Only what is within the for block gets repeated, not its initialiser.
Your 2nd option does the same as the 1st, just with an unnamed temporary variable holding the result of GetMyList(). If anything, it might be more efficient for that reason... though a good optimiser would make both pieces of code equivalent anyway.
As mentioned in the comments, a debugger would've made this abundantly clear, and is an invaluable tool for countless other reasons.

Method #1 leaves you with a reference to the list in the scope of the rest of the method.
Method #2 creates a variable behind the scenes referencing the list, but that variable is out of scope after the for loop
For scoping I would prefer #2, but I am also impartial to succinct code. If GetMyList returns a reference type such as a List<T> or array, this could leave the door open to some unintended side effects.
Public Sub Foo()
Dim someClass As New SomeClass()
' this variable stays in scope after the following For Each loop
Dim list = someClass.GetMyList()
For Each item In list
Console.Write(item)
Next
Console.WriteLine()
' now we can sort the backing field - did you intend for this to happen?
list.Sort()
' the following For Each loop doesn't leave any reference behind
For Each item In someClass.GetMyList()
Console.Write(item)
Next
End Sub
Private Class SomeClass
Private _list As List(Of Integer) = {3, 2, 1}.ToList()
Public Function GetMyList() As List(Of Integer)
Return _list
End Function
End Class
Foo() writes:
321
123
So you can actually manipulate the backing field after you were supposedly done with it!

let take a simple example
there is not much information, i will assume GetMyList is a list(of integer)
Module Module1
Sub Main()
test1()
test2()
Console.ReadKey(False)
End Sub
Sub test1()
Dim list = SomeClass.GetMyList()
For Each item In list
Console.WriteLine(item)
Next
End Sub
Sub test2()
For Each item In SomeClass.GetMyList()
Console.WriteLine(item)
Next
End Sub
End Module
Class SomeClass
Public Shared Function GetMyList() As List(Of Integer)
Dim aList = New List(Of Integer)
aList.Add(1)
aList.Add(2)
aList.Add(3)
Console.WriteLine("I am in the function now")
Return aList
End Function
End Class
you can run it yourself to see the behavior
now let look at the actual IL (compiled with debug)
test1 code;
.method public static
void test1 () cil managed
{
// Method begins at RVA 0x2120
// Code size 64 (0x40)
.maxstack 1
.locals init (
[0] class [mscorlib]System.Collections.Generic.List`1<int32> list,
[1] int32 item,
[2] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> VB$t_struct$L0,
[3] bool VB$CG$t_bool$S0
)
IL_0000: nop
IL_0001: call class [mscorlib]System.Collections.Generic.List`1<int32> ConsoleApplication1.SomeClass::GetMyList()
IL_0006: stloc.0
IL_0007: nop
.try
{
IL_0008: ldloc.0
IL_0009: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_000e: stloc.2
IL_000f: br.s IL_0021
// loop start (head: IL_0021)
IL_0011: ldloca.s VB$t_struct$L0
IL_0013: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0018: stloc.1
IL_0019: ldloc.1
IL_001a: call void [mscorlib]System.Console::WriteLine(int32)
IL_001f: nop
IL_0020: nop
IL_0021: ldloca.s VB$t_struct$L0
IL_0023: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0028: stloc.3
IL_0029: ldloc.3
IL_002a: brtrue.s IL_0011
// end loop
IL_002c: nop
IL_002d: leave.s IL_003e
} // end .try
finally
{
IL_002f: ldloca.s VB$t_struct$L0
IL_0031: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0037: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003c: nop
IL_003d: endfinally
} // end handler
IL_003e: nop
IL_003f: ret
} // end of method Module1::test1
test2 code;
.method public static
void test2 () cil managed
{
// Method begins at RVA 0x217c
// Code size 62 (0x3e)
.maxstack 1
.locals init (
[0] int32 item,
[1] valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32> VB$t_struct$L0,
[2] bool VB$CG$t_bool$S0
)
IL_0000: nop
IL_0001: nop
.try
{
IL_0002: call class [mscorlib]System.Collections.Generic.List`1<int32> ConsoleApplication1.SomeClass::GetMyList()
IL_0007: callvirt instance valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List`1<int32>::GetEnumerator()
IL_000c: stloc.1
IL_000d: br.s IL_001f
// loop start (head: IL_001f)
IL_000f: ldloca.s VB$t_struct$L0
IL_0011: call instance !0 valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::get_Current()
IL_0016: stloc.0
IL_0017: ldloc.0
IL_0018: call void [mscorlib]System.Console::WriteLine(int32)
IL_001d: nop
IL_001e: nop
IL_001f: ldloca.s VB$t_struct$L0
IL_0021: call instance bool valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>::MoveNext()
IL_0026: stloc.2
IL_0027: ldloc.2
IL_0028: brtrue.s IL_000f
// end loop
IL_002a: nop
IL_002b: leave.s IL_003c
} // end .try
finally
{
IL_002d: ldloca.s VB$t_struct$L0
IL_002f: constrained. valuetype [mscorlib]System.Collections.Generic.List`1/Enumerator<int32>
IL_0035: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003a: nop
IL_003b: endfinally
} // end handler
IL_003c: nop
IL_003d: ret
} // end of method Module1::test2
only thing different is when the GetMyList reference is called / loaded in memory
first one load it in a local variable, the second one just load it when the loop start
so both scenario will do the same thing.

Related

Declaration inside the loop will maintain the memory space or create multiple space?

Consider the method which i written as a sample of this question:
Public Sub Myfun()
Dim loopcount As Integer
For loopcount = 0 To 1000
Dim x as integer = 2
x * = loopcount
ListBox1.Items.Add (x)
Next
End Sub
My question is that this type of declaration will create how much of memory space for x.
how it is being identified if it creates 1000 separate spaces with the same name as x?
No, x is a local variable albeit declared inside a loop and this affects its visibility not the memory space occupied by that variable.
Looking at the IL code generated when you remove the ListBox1 object it is pretty clear what happens
IL_0000: ldc.i4.0 // store 0 on top of stack
IL_0001: stloc.0 // load first local variable (loopcount) with top of stack
IL_0002: ldc.i4.2 // store 2 on top of stack
IL_0003: stloc.1 // load second local variable (x) with top of the stack
IL_0004: ldloc.1 // store x on top of the stack
IL_0005: ldloc.0 // store loopcount on top of the stack
IL_0006: mul.ovf // multiply with overflow check the last two integers on stack
IL_0007: stloc.1 // store the top of stack in second local variable (x)
IL_0008: ldloc.0 // following is the logic of the for .... loop
IL_0009: ldc.i4.1
IL_000A: add.ovf
IL_000B: stloc.0
IL_000C: ldloc.0
IL_000D: ldc.i4 E8 03 00 00
IL_0012: ble.s IL_0002 // Repeat the loop from IL_002

F# "for loop" optimization

Code example:
let foo1 (arr : int[]) =
for i = 0 to arr.Length-1 do
arr.[i] <- i
let foo2 (arr : int[]) =
for i in [0..arr.Length-1] do
arr.[i] <- i
I thought that this functions should be equivalent to each other (in terms of performance). But if we look into IL listing, we'll see:
First function, 15 lines, no dynamic allocations, no try operator, no virtual calling:
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: stloc.0
IL_0003: br.s IL_0011
// loop start (head: IL_0011)
IL_0005: ldarg.0
IL_0006: ldloc.0
IL_0007: ldloc.0
IL_0008: stelem.any [mscorlib]System.Int32
IL_000d: ldloc.0
IL_000e: ldc.i4.1
IL_000f: add
IL_0010: stloc.0
IL_0011: ldloc.0
IL_0012: ldarg.0
IL_0013: ldlen
IL_0014: conv.i4
IL_0015: blt.s IL_0005
// end loop
IL_0017: ret
and second one - almost 100 lines, lots of allocations/deallocations, callings of virtual functions, lots of try/Dispose:
IL_0000: nop
IL_0001: ldc.i4.0
IL_0002: ldc.i4.1
IL_0003: ldarg.0
IL_0004: ldlen
IL_0005: conv.i4
IL_0006: ldc.i4.1
IL_0007: sub
IL_0008: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [FSharp.Core]Microsoft.FSharp.Core.Operators/OperatorIntrinsics::RangeInt32(int32, int32, int32)
IL_000d: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [FSharp.Core]Microsoft.FSharp.Core.Operators::CreateSequence<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0012: call class [FSharp.Core]Microsoft.FSharp.Collections.FSharpList`1<!!0> [FSharp.Core]Microsoft.FSharp.Collections.SeqModule::ToList<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>)
IL_0017: stloc.0
IL_0018: ldloc.0
IL_0019: unbox.any class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>
IL_001e: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
IL_0023: stloc.1
.try
{
// loop start (head: IL_0024)
IL_0024: ldloc.1
IL_0025: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_002a: brfalse.s IL_003e
IL_002c: ldloc.1
IL_002d: callvirt instance !0 class [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
IL_0032: stloc.3
IL_0033: ldarg.0
IL_0034: ldloc.3
IL_0035: ldloc.3
IL_0036: stelem.any [mscorlib]System.Int32
IL_003b: nop
IL_003c: br.s IL_0024
// end loop
IL_003e: ldnull
IL_003f: stloc.2
IL_0040: leave.s IL_005b
} // end .try
finally
{
IL_0042: ldloc.1
IL_0043: isinst [mscorlib]System.IDisposable
IL_0048: stloc.s 4
IL_004a: ldloc.s 4
IL_004c: brfalse.s IL_0058
IL_004e: ldloc.s 4
IL_0050: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0055: ldnull
IL_0056: pop
IL_0057: endfinally
IL_0058: ldnull
IL_0059: pop
IL_005a: endfinally
} // end handler
IL_005b: ldloc.2
IL_005c: pop
IL_005d: ret
My question is why does F# compiler uses so complicated code for foo2? Why does it use an IEnumerable to implement so trivial loop?
In the 2nd example if you use range expression, it will be converted into normal for loop:
let foo2 (arr : int[]) =
for i in 0..arr.Length-1 do
arr.[i] <- i
and become equivalent to foo1.
I quote Section 6.3.12 Range Expressions in F# language specs:
A sequence iteration expression of the form for var in expr1 .. expr2
do expr3 done is sometimes elaborated as a simple for loop-expression
(ยง6.5.7).
However, your 2nd example is more like:
let foo2 (arr : int[]) =
let xs = [0..arr.Length-1] (* A new list is created *)
for i in xs do
arr.[i] <- i
where you have created a new list explicitly.
What your seeing is the standard difference between using an index based enumeration and an IEnumerable based enumeration (or in C# terms for vs foreach).
In the second sample the expression [0..arr.Length-1] is creating a collection and F# is using IEnumerable<T> to enumerate the values. Part of this enumeration style involves the use of IEnumerator<T> which implements IDisposable. The try / finally block you are seeing is generated to ensure that the IDisposable::Dispose method is called at the end of the enumeration even in the face of an exception.
Could F# optimize the second example into the first and avoid all of the extra overhead? It's possible that they could do this optimization. Essentially peek through the range expression, not it's just a simple numeric range and hence generate the equivalent for code.
Should F# optimize the second example. My vote would be no. Features like this often look trivial from the outside but actually implementing them, and more importantly maintaining them, can be rather expensive. An astute user could always convert their code back to the standard for version and avoid the IEnumerable<T> overhead (should the profiler reveal it to be an issue). Not implementing the optimization frees up the F# team to implement other awesome features.

Code Coverage: Why is end marker red (End If, End Try, ...)

I use MS-Test with Visual Studio 2010 and Visual Basic.
In the following function the Code Coverage tells me, that there is one unchecked block and the line with the "End Try" is red (see http://lts.cr/BVvP):
Private Function GetLatestVersionInfoForAsync()
Try
Return GetLatestVersionInfo()
Catch ex As Exception
RaiseEvent UnhandledAsyncException(Me, New UnhandledExceptionEventArgs(ex, False))
Return New VersionInfo() With {.ExceptionOccoured = True, .Exception = ex}
End Try
End Function
So, why is this "End Try" line an uncovered (red) block (the same happens to "End If" at the end of a function)?
Another question I have: Is there any resource that explains the different colors of in the code coverage results (blue is clear, but I have seen yellow, dark and light red, ...).
Thanks!
Further to Daniel's point on sequence points it's worth looking at this further. If we take a simple function that repeats what you are doing
07 Function Method() As String
08 Try
09 Return ""
10 Catch ex As Exception
11 Return ""
12 End Try
13 End Function
In Debug we get the following sequence points (I am using OpenCover for this)
<SequencePoints>
<SequencePoint offset="0" ordinal="0" uspid="261" vc="0" ec="32" el="7" sc="5" sl="7"/>
<SequencePoint offset="1" ordinal="1" uspid="262" vc="0" ec="12" el="8" sc="9" sl="8"/>
<SequencePoint offset="2" ordinal="2" uspid="263" vc="0" ec="22" el="9" sc="13" sl="9"/>
<SequencePoint offset="19" ordinal="3" uspid="264" vc="0" ec="30" el="10" sc="9" sl="10"/>
<SequencePoint offset="20" ordinal="4" uspid="265" vc="0" ec="22" el="11" sc="13" sl="11"/>
<SequencePoint offset="40" ordinal="5" uspid="266" vc="0" ec="16" el="12" sc="9" sl="12"/>
<SequencePoint offset="41" ordinal="6" uspid="267" vc="0" ec="17" el="13" sc="5" sl="13"/>
</SequencePoints>
(where sl = start line, el = end line, sc = start column, ec = end column and offset = IL offset in decimal)
However these only make sense when you look at the IL
.method public static
string Method () cil managed
{
// Method begins at RVA 0x272c
// Code size 43 (0x2b)
.maxstack 2
.locals init (
[0] string Method,
[1] class [mscorlib]System.Exception ex
)
IL_0000: nop
IL_0001: nop
.try
{
IL_0002: ldstr ""
IL_0007: stloc.0
IL_0008: leave.s IL_0029
IL_000a: leave.s IL_0028
} // end .try
catch [mscorlib]System.Exception
{
IL_000c: dup
IL_000d: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_0012: stloc.1
IL_0013: nop
IL_0014: ldstr ""
IL_0019: stloc.0
IL_001a: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_001f: leave.s IL_0029
IL_0021: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_0026: leave.s IL_0028
} // end handler
IL_0028: nop
IL_0029: ldloc.0
IL_002a: ret
} // end of method Module1::Method
Now as you can see the End Try line you are concerned about would only be marked as hit if you hit the IL instruction at offset 40 (IL_0028) however when you look at the IL produced I cant see how you would ever get there due to the odd IL produced (leave.s is a small jump like instruction that is used to exit try/catch/finally blocks) and if you follow the code you see that you will always reach a leave.s that jumps to IL_0029 first.
In release the IL changes
.method public static
string Method () cil managed
{
// Method begins at RVA 0x2274
// Code size 30 (0x1e)
.maxstack 2
.locals init (
[0] string Method,
[1] class [mscorlib]System.Exception ex
)
.try
{
IL_0000: ldstr ""
IL_0005: stloc.0
IL_0006: leave.s IL_001c
} // end .try
catch [mscorlib]System.Exception
{
IL_0008: dup
IL_0009: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_000e: stloc.1
IL_000f: ldstr ""
IL_0014: stloc.0
IL_0015: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_001a: leave.s IL_001c
} // end handler
IL_001c: ldloc.0
IL_001d: ret
} // end of method Module1::Method
and so do the sequence points
<SequencePoints>
<SequencePoint offset="0" ordinal="0" uspid="33" vc="0" ec="22" el="9" sc="13" sl="9"/>
<SequencePoint offset="15" ordinal="1" uspid="34" vc="0" ec="22" el="11" sc="13" sl="11"/>
<SequencePoint offset="28" ordinal="2" uspid="35" vc="0" ec="17" el="13" sc="5" sl="13"/>
</SequencePoints>
So you sort of loose either way as now you will never see your try/catch lines marked covered
So lets try changing your code as suggested by Hans and go back to debug (because that is where you will be running coverage from usually)
15 Function Method2() As String
16 Dim x As String
17 Try
18 x = ""
19 Catch ex As Exception
20 x = ""
21 End Try
22 Return x
23 End Function
Again we look at the sequence points
<SequencePoints>
<SequencePoint offset="0" ordinal="0" uspid="268" vc="0" ec="33" el="15" sc="5" sl="15"/>
<SequencePoint offset="1" ordinal="1" uspid="269" vc="0" ec="12" el="17" sc="9" sl="17"/>
<SequencePoint offset="2" ordinal="2" uspid="270" vc="0" ec="19" el="18" sc="13" sl="18"/>
<SequencePoint offset="17" ordinal="3" uspid="271" vc="0" ec="30" el="19" sc="9" sl="19"/>
<SequencePoint offset="18" ordinal="4" uspid="272" vc="0" ec="19" el="20" sc="13" sl="20"/>
<SequencePoint offset="31" ordinal="5" uspid="273" vc="0" ec="16" el="21" sc="9" sl="21"/>
<SequencePoint offset="32" ordinal="6" uspid="274" vc="0" ec="17" el="22" sc="9" sl="22"/>
<SequencePoint offset="36" ordinal="7" uspid="275" vc="0" ec="17" el="23" sc="5" sl="23"/>
</SequencePoints>
and the IL
.method public static
string Method2 () cil managed
{
// Method begins at RVA 0x282c
// Code size 38 (0x26)
.maxstack 2
.locals init (
[0] string Method2,
[1] string x,
[2] class [mscorlib]System.Exception ex
)
IL_0000: nop
IL_0001: nop
.try
{
IL_0002: ldstr ""
IL_0007: stloc.1
IL_0008: leave.s IL_001f
} // end .try
catch [mscorlib]System.Exception
{
IL_000a: dup
IL_000b: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::SetProjectError(class [mscorlib]System.Exception)
IL_0010: stloc.2
IL_0011: nop
IL_0012: ldstr ""
IL_0017: stloc.1
IL_0018: call void [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.ProjectData::ClearProjectError()
IL_001d: leave.s IL_001f
} // end handler
IL_001f: nop
IL_0020: ldloc.1
IL_0021: stloc.0
IL_0022: br.s IL_0024
IL_0024: ldloc.0
IL_0025: ret
} // end of method Module1::Method2
So for your End Try to be covered we need line 21 to be hit and that is offset 31 (IL_001F) and as we can see both leave.s instructions jump to that point so now that line will be marked as covered.
So both Hans and Daniel are correct and I hope the above explains why
Before control passes that End Try line, it reaches the Return line and exits the function. So (as far as Code coverage is concerned) you never reach that line. Not that it is any problem in this case.
A workaround would be to hold that VersionInfo in a single temp variable and return that after the End Try. A guess (I'm used to C#, not VB):
Private Function GetLatestVersionInfoForAsync()
Dim vi As VersionInfo
Try
vi = GetLatestVersionInfo()
Catch ex As Exception
RaiseEvent UnhandledAsyncException(Me, New UnhandledExceptionEventArgs(ex, False))
vi = New VersionInfo() With {.ExceptionOccoured = True, .Exception = ex}
End Try
Return vi
End Function
The PDB file of your assembly contains the information which IL instructions corresponds to which line(s) of your original source code. This piece of information is called a sequence point.
But not every line in your code corresponds exactly to one sequence point.
Your test coverage is calculated based on the sequence points, so it could happen that lines of your code appear uncovered although they were executed during your test.
I have never used MS-Test but it will be flagging "New VersionInfo()" as unchecked.

ILGenerator emits a Break instruction when storing elements in an array

I'm generating dynamic types using ILGenerator.Emit. I am generating a method body that will store the types of the method arguments in an array. To actually store the elements in the array I am looping through parameters of a given method and building up the necessary IL to store the elements. On the second iteration a Break instruction appears after the Stelem.ref (L_003d below) instruction. This always happens on the second iteration and I cannot figure out why. Here is the code:
ilGenerator.Emit(OpCodes.Ldc_I4, exampleMethod.GetParameters().Length);
ilGenerator.Emit(OpCodes.Newarr, typeof(Type));
ilGenerator.Emit(OpCodes.Stloc, typeArray);
for (int idx = 0; idx < exampleMethod.GetParameters().Length; idx++)
{
ilGenerator.Emit(OpCodes.Ldloc, typeArray);
ilGenerator.Emit(OpCodes.Ldc_I4, idx);
ilGenerator.Emit(OpCodes.Ldarg, idx + 1);
ilGenerator.Emit(OpCodes.Box, typeof(int));
ilGenerator.EmitCall(OpCodes.Callvirt, typeof(object).GetMethod("GetType"), null);
ilGenerator.Emit(OpCodes.Stelem_Ref, idx);//second iteration causes a break to be output in the IL
}
ilGenerator.Emit(OpCodes.Ret);
and the IL output is here
.method public virtual instance int32 Add3(int32, int32, int32) cil managed
{
.maxstack 3
.locals init (
[0] class [mscorlib]System.Type[] typeArray)
L_0000: ldc.i4 3
L_0005: newarr [mscorlib]System.Type
L_000a: stloc.0
L_000b: ldloc.0
L_000c: ldc.i4 0
L_0011: ldarg A_0
L_0015: nop
L_0016: nop
L_0017: box int32
L_001c: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
L_0021: stelem.ref
L_0022: nop
L_0023: nop
L_0024: nop
L_0025: nop
L_0026: ldloc.0
L_0027: ldc.i4 1
L_002c: ldarg A_1
L_0030: nop
L_0031: nop
L_0032: box int32
L_0037: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
L_003c: stelem.ref
**L_003d: break**
L_003e: nop
L_003f: nop
L_0040: nop
L_0041: ldloc.0
L_0042: ldc.i4 2
L_0047: ldarg A_2
L_004b: nop
L_004c: nop
L_004d: box int32
L_0052: callvirt instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
L_0057: stelem.ref
L_0058: ldarg.0
L_0059: nop
L_005a: nop
L_005b: nop
L_005c: ret
}
Any pointers or suggestions would be greatly appreciated.
Many thanks
Dermot
The opcode for break is 0x01, which incidentally is also the idx value you pass as a parameter to the stelem.ref emit. Note that there's an extra ldarg.0 on the third iteration (where idx is 2).
You should not specify a parameter to the stelem emit.

Or versus OrElse

What's the difference between or and OrElse?
if temp is dbnull.value or temp = 0
produces the error:
Operator '=' is not defined for type 'DBNull' and type 'Integer'.
while this one works like a charm!?
if temp is dbnull.value OrElse temp = 0
OrElse is a short-circuiting operator, Or is not.
By the definition of the boolean 'or' operator, if the first term is True then the whole is definitely true - so we don't need to evaluate the second term.
OrElse knows this, so doesn't try and evaluate temp = 0 once it's established that temp Is DBNull.Value
Or doesn't know this, and will always attempt to evaluate both terms. When temp Is DBNull.Value, it can't be compared to zero, so it falls over.
You should use... well, whichever one makes sense.
This is the same behaviour as with C#, where everyone uses the Coditional Or (||) and the Conditional And (&&), where you also have the normal Or (|) and normal And (&). So comparing C# to VB.Net is:
| => Or
|| => OrElse
& => And
&& => AndAlso
The condifitonal boolean operators are very usefull preventing nested if constructions. But sometimes the normal boolean operators are needed to ensure hitting both code paths.
OrElse is short circuited, this means that only one side of the expression will be tested if the first side is a match.
Just like AndAlso will only test one side of the expression if the first half is a fail.
OrElse evaluate first expression then if its true it will proceed to the statement while OR evaluates two expressions before it will proceed to their statement.
Example:
Textbox1.Text= 4
Textbox2.Text= ""
Using OrElse
If TextBox1.Text > 2 OrElse TextBox2.Text > 3 Then
MsgBox("True")
End If
Result is: TRUE
Using OR
If TextBox1.Text > 2 Or TextBox2.Text > 3 Then
MsgBox("True")
End If
Result is: Error cannot convert string to double.
(I've looked at other answers and realized I was terribly wrong)
The OrElse operator "performs short-circuiting logical disjunction on two expressions", that is to say: if the left operand is true and so the entire expression is guaranteed to be true the right operand won't even be evaluated (this is useful in cases like:
string a;
//...
if (a is null) or (a = "Hi") //...
to avoid a NullReferenceException throw by the right-hand operand.
I'm sincerely astonished that this (lazy evaluation) isn't the default behaviour of or and and as it is in C/C++ and C# (and many other languages...)
The Bert' s answer is not very accurate. The '|' or '&' is logical operator, in C #, it always treat as bit operator, please see the following code as example
static void Main()
{
object a = null;
int b = 3;
if (a == null | a.ToString() == "sdffd")
{
Console.WriteLine("dddd");
}
Console.WriteLine(b | b);
Console.Read();
}
The following is IL
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 62 (0x3e)
.maxstack 3
.locals init ([0] object a,
[1] int32 b,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldc.i4.3
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: ldnull
IL_0007: ceq
IL_0009: ldloc.0
IL_000a: callvirt instance string [mscorlib]System.Object::ToString()
IL_000f: ldstr "sdffd"
IL_0014: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0019: or
IL_001a: ldc.i4.0
IL_001b: ceq
IL_001d: stloc.2
IL_001e: ldloc.2
IL_001f: brtrue.s IL_002e
IL_0021: nop
IL_0022: ldstr "dddd"
IL_0027: call void [mscorlib]System.Console::WriteLine(string)
IL_002c: nop
IL_002d: nop
IL_002e: ldloc.1
IL_002f: ldloc.1
IL_0030: or
IL_0031: call void [mscorlib]System.Console::WriteLine(int32)
IL_0036: nop
IL_0037: call int32 [mscorlib]System.Console::Read()
IL_003c: pop
IL_003d: ret
} // end of method Program::Main
when you use || to test "a == null" and "a.ToString() == "sdffd", the IL will be
.method private hidebysig static void Main() cil managed
{
.entrypoint
// Code size 63 (0x3f)
.maxstack 2
.locals init ([0] object a,
[1] int32 b,
[2] bool CS$4$0000)
IL_0000: nop
IL_0001: ldnull
IL_0002: stloc.0
IL_0003: ldc.i4.3
IL_0004: stloc.1
IL_0005: ldloc.0
IL_0006: brfalse.s IL_001d
IL_0008: ldloc.0
IL_0009: callvirt instance string [mscorlib]System.Object::ToString()
IL_000e: ldstr "sdffd"
IL_0013: call bool [mscorlib]System.String::op_Equality(string,
string)
IL_0018: ldc.i4.0
IL_0019: ceq
IL_001b: br.s IL_001e
IL_001d: ldc.i4.0
IL_001e: stloc.2
IL_001f: ldloc.2
IL_0020: brtrue.s IL_002f
IL_0022: nop
IL_0023: ldstr "dddd"
IL_0028: call void [mscorlib]System.Console::WriteLine(string)
IL_002d: nop
IL_002e: nop
IL_002f: ldloc.1
IL_0030: ldloc.1
IL_0031: or
IL_0032: call void [mscorlib]System.Console::WriteLine(int32)
IL_0037: nop
IL_0038: call int32 [mscorlib]System.Console::Read()
IL_003d: pop
IL_003e: ret
} // end of method Program::Main
Now you can see the difference, please don't think the '|' or 'and' as conditional operator, it just a logical operator, I don't think there is necessary to use it to judge condition
The reason the compilation fails in the example is the order of operations.
The expression parser is trying to evaluate "dbnull.value or temp" first.
if temp is (dbnull.value or temp) = 0
The error is here, because you can't do a bitwise OR between an integer (temp) and dbnull.value.
OrElse fixes this, not because it's short-circuited, but because it's lower on the order of operations, and so "temp is dbnull.value" and "3=0" are being evaluated first, rather than the parser trying to compare dbNull and temp.
So the evaluation with OrElse works like you're expecting: (assume temp=3)
if temp is dbnull.value OrElse temp = 0 then
if 3 is dbnull.value OrElse 3 = 0 then
if false OrElse 3=0 then
if false OrElse false then
if false then
This was actually on an entry exam at a software company I used to work for, and it was a common problem I used to encounter in VB6. So it's a good idea to parenthesize your sub-expressions when using boolean operators:
This would have compiled properly:
if (temp is dbnull.value) Or (temp = 0) then
Although, as everyone has already pointed out, OrElse and AndAlso are really the correct operators to use in this context.
Unless your code logic requires the short-circuiting behavior OrElse provides, I would lean toward using the Or operator because:
Using "Or" is simple and requires less typing.
The computational time savings of using OrElse is negligible in most cases.
Most importantly, using OrElse can hide errors in later clauses that may not be initially revealed until those conditions would eventually be met by the program logic.