Brendan Enrick's Blog

Daily Software Development.


Fun with IL DASM and Duck Typing

by Brendan Enrick Wednesday, February 1 2012 10:00

So as a bit of fun, since I just wrote a post about duck typing and foreach loops, I thought I would take a look at what’s happening under the hood when we use this trick. To start with, I will have two classes, which each have GetEnumerator methods. The first one is using DuckTyping, so it doesn’t implement any interfaces. The second one is implementing the IEnumerable interface. Finally, I have a third class with a method that just uses a foreach loop over instances of the other two classes.

The code for my little example looks like this:

public class DuckCollection
{
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}

public class EnumerableCollection : IEnumerable
{
public IEnumerator GetEnumerator()
{
throw new NotImplementedException();
}
}

public class ForEacher
{
public void DoForEach()
{
var duckCollection = new DuckCollection();
foreach (var thing1 in duckCollection)
{

}

var enumerableCollection = new EnumerableCollection();
foreach (var thing2 in enumerableCollection)
{

}
}
}

After I compile this code, I’ve opened it up with IL DASM, so I can see what the generated IL is for this code.
.method public hidebysig instance void  DoForEach() cil managed
{
// Code size 146 (0x92)
.maxstack 2
.locals init ([0] class DuckTypingILCode.DuckCollection duckCollection,
[1] object thing1,
[2] class DuckTypingILCode.EnumerableCollection enumerableCollection,
[3] object thing2,
[4] class [mscorlib]System.Collections.IEnumerator CS$5$0000,
[5] bool CS$4$0001,
[6] class [mscorlib]System.IDisposable CS$0$0002)
IL_0000: nop
IL_0001: newobj instance void DuckTypingILCode.DuckCollection::.ctor()
IL_0006: stloc.0
IL_0007: nop
IL_0008: ldloc.0
IL_0009: callvirt instance class [mscorlib]System.Collections.IEnumerator DuckTypingILCode.DuckCollection::GetEnumerator()
IL_000e: stloc.s CS$5$0000
.try
{
IL_0010: br.s IL_001c
IL_0012: ldloc.s CS$5$0000
IL_0014: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_0019: stloc.1
IL_001a: nop
IL_001b: nop
IL_001c: ldloc.s CS$5$0000
IL_001e: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_0023: stloc.s CS$4$0001
IL_0025: ldloc.s CS$4$0001
IL_0027: brtrue.s IL_0012
IL_0029: leave.s IL_0048
} // end .try
finally
{
IL_002b: ldloc.s CS$5$0000
IL_002d: isinst [mscorlib]System.IDisposable
IL_0032: stloc.s CS$0$0002
IL_0034: ldloc.s CS$0$0002
IL_0036: ldnull
IL_0037: ceq
IL_0039: stloc.s CS$4$0001
IL_003b: ldloc.s CS$4$0001
IL_003d: brtrue.s IL_0047
IL_003f: ldloc.s CS$0$0002
IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0046: nop
IL_0047: endfinally
} // end handler
IL_0048: nop
IL_0049: newobj instance void DuckTypingILCode.EnumerableCollection::.ctor()
IL_004e: stloc.2
IL_004f: nop
IL_0050: ldloc.2
IL_0051: callvirt instance class [mscorlib]System.Collections.IEnumerator DuckTypingILCode.EnumerableCollection::GetEnumerator()
IL_0056: stloc.s CS$5$0000
.try
{
IL_0058: br.s IL_0064
IL_005a: ldloc.s CS$5$0000
IL_005c: callvirt instance object [mscorlib]System.Collections.IEnumerator::get_Current()
IL_0061: stloc.3
IL_0062: nop
IL_0063: nop
IL_0064: ldloc.s CS$5$0000
IL_0066: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
IL_006b: stloc.s CS$4$0001
IL_006d: ldloc.s CS$4$0001
IL_006f: brtrue.s IL_005a
IL_0071: leave.s IL_0090
} // end .try
finally
{
IL_0073: ldloc.s CS$5$0000
IL_0075: isinst [mscorlib]System.IDisposable
IL_007a: stloc.s CS$0$0002
IL_007c: ldloc.s CS$0$0002
IL_007e: ldnull
IL_007f: ceq
IL_0081: stloc.s CS$4$0001
IL_0083: ldloc.s CS$4$0001
IL_0085: brtrue.s IL_008f
IL_0087: ldloc.s CS$0$0002
IL_0089: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_008e: nop
IL_008f: endfinally
} // end handler
IL_0090: nop
IL_0091: ret
} // end of method ForEacher::DoForEach


 

Yes, it’s a bit ugly, but you will notice that other than naming, the code is just repeated twice. This is of course what we would expect, since the code should treat it the same regardless. All the foreach loop needed was to be able to get the object from the GetEnumerator method. Once it has that, it just uses the enumerator to do all of the work. This means that either class should work just as well.

This is the repeated line of code that shows up in two places to get the enumerator it will be using for its MoveNext() and GetCurrent() methods.

IL_0009:  callvirt   instance class [mscorlib]System.Collections.IEnumerator DuckTypingILCode.DuckCollection::GetEnumerator()
IL_0051: callvirt instance class [mscorlib]System.Collections.IEnumerator DuckTypingILCode.EnumerableCollection::GetEnumerator()

 

Now if you’re really paying attention, you will have looked at the Try-Finally block that is in the code to handle our IDisposables. That’s another neat thing happening behind the scenes, but that’s another blog post entirely.

If you would like to see another cool thing in the .NET Framework, you should check out what else you can do with Null Instance methods if you change those callvirts.

Tagged as: , , , ,

Comments

3/14/2012 5:24:26 AM #

Chathura

Very helpful.. continue writing... goodluck

Chathura Sri Lanka

3/14/2012 5:26:20 AM #

Chathura

Try to write your post for Novice Programmars also... some post bit tough to beginners like me Smile

Chathura Sri Lanka

Comments are closed