This section demonstrates how to iterate all instructions of an assembly.
You should:
- Read the assembly (see How to: Read an Assembly), i.e. load into into a Code Model object tree.
- Use the
ModuleDeclaration.GetDeclarationEnumeratorwith the TokenType.MethodDef argument to get an enumerator of all method definitions (MethodDefDeclaration) in the current module. - Test whether the method has a body (abstract and external methods have no).
- Call the
MethodBodyDeclaration.ForEachInstructionand 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:
- The use of
ModuleDeclaration.GetDeclarationEnumeratorto get a flat list of all declarations of a given type. - The use of
MethodBodyDeclaration.ForEachInstructionto unconditionally iterate all instructions of the method body. - Iterating instruction blocks and instruction sequences using linked-list semantics.
- Entering and leaving contexts of the InstructionReader using stack semantics.
- Reading individual instructions thanks to the reader semantics
of the
InstructionReaderclass. - Accessing properties of the current instruction of an
InstructionReader.