This section demonstrates how to iterate all instructions of an assembly.

You should:

  1. Read the assembly (see How to: Read an Assembly), i.e. load into into a Code Model object tree.
  2. Use the ModuleDeclaration.GetDeclarationEnumerator with the TokenType.MethodDef argument to get an enumerator of all method definitions (MethodDefDeclaration) in the current module.
  3. Test whether the method has a body (abstract and external methods have no).
  4. Call the MethodBodyDeclaration.ForEachInstruction and give as an argument a delegate to the method that will be called for each instruction.

Sample

IEnumerator<MetadataDeclaration> methodEnumerator = module.GetDeclarationEnumerator(TokenType.MethodDef);
while (methodEnumerator.MoveNext())
{
MethodDefDeclaration = (MethodDefDeclaration)methodEnumerator.Current;
if (MethodDefDeclaration.HasBody)
{
MethodDefDeclaration.Body.ForEachInstruction(
delegate(InstructionReader instructionReader)
{
if (instructionReader.OperandType == System.Reflection.Emit.OperandType.InlineMethod)
{
// Do something here.
}
});
}

}

How does it work?

There is in fact very little magic behind the MethodBodyDeclaration.ForEachInstruction. Let have a look in the source code.

public void ForEachInstruction( InstructionAction action )
{
InstructionReader reader = this.GetInstructionReader();
ForEachInstructionProcessBlock( this.rootInstructionBlock, action, reader );
}

The algorithm is in fact a recursion on instruction blocks. The base method ForEachInstruction first creates an InstructionReader that will be shared during the whole method call. Then it calls the recursive method, ForEachInstructionProcessBlock, for the root instruction block.

The InstructionReader provides access to instructions. It has the semantics of a read-only, forward-only cursor, just like data readers of ADO.NET. However, before using the InstructionReader as a cursor, you need to position it to the proper block, then to the proper sequence. For this purpose you use Enter and Leave methods.

At the beginning, we instruct the instruction reader to enter the current instruction block. This allows the reader to bind local variables to the proper symbols.

reader.EnterInstructionBlock( block );

At the end of the method, we leave the context of the current block. The reader pops up the context of the parent block.

reader.LeaveInstructionBlock();

A block may contain either children blocks either instruction sequences. In case that the current block contains children blocks, we simply recur on them.

if (block.HasChildrenBlocks)
{
InstructionBlock child = block.FirstChildBlock;
while (child != null)
{
ForEachInstructionProcessBlock(child, action, reader);
child = child.NextSiblingBlock;
}
}

Note that blocks are organized as a double-linked list.

If the current block contain sequences, we iterate on sequences. For each sequence, we instruct the instruction reader to enter in the current sequence, iterate all instructions and leave the context of the sequence. Finally, for each instruction we call the delegate provided by the caller.

else
{
InstructionSequence sequence = block.FirstInstructionSequence;
while (sequence != null)
{
reader.EnterInstructionSequence(sequence);
while (reader.ReadInstruction())
{
action(reader);
}
reader.LeaveInstructionSequence();
sequence = sequence.NextSiblingSequence;
}
}

Summary

In this section, we have demonstrated the following techniques: