How to View the IL of a .NET Assembly (and Why)
How to view the IL of a .NET assembly, what CIL actually is, and when reading IL beats decompiled C# — with a small, correct C#-to-IL worked example.
Most of the time, when you open an assembly you did not write, you want readable C#. A decompiler gives you that, and it is the right default. But every so often the decompiled C# does something that makes you squint — a cast that should not be there, a call you did not expect, output that reads oddly but is “technically correct.” That is the moment to stop reading the reconstruction and look at what the assembly actually contains: the IL.
This post is about the layer underneath the C#: what IL is, how to view it, and — more usefully — when reading IL is genuinely better than reading decompiled C#. I will keep the theory short and finish with a small, correct worked example you can reproduce.
TL;DR: IL is the real instruction set inside every managed DLL; decompiled C# is a reconstruction of it. To view IL, open the assembly in a tool like Glass.NET and flip any member from its C# view to its IL view (or use the SDK’s ildasm.exe). Read IL when the C# surprises you, when you need to see hidden behavior like boxing, or when the C# will not reconstruct cleanly.
What IL actually is
When you build a C# project, the compiler does not emit machine code. It emits Common Intermediate Language — CIL, often just called IL (and historically MSIL). IL is a compact, stack-based instruction set: instead of registers, most operations push and pop values on an evaluation stack. Alongside the IL, the compiler writes a rich metadata table describing every type, method, field and parameter by name and signature. Both live inside the .dll or .exe.
Native code is not produced until runtime. When a method is first called, the JIT (just-in-time) compiler turns its IL into machine instructions for the current CPU. This is why the same managed DLL runs on different architectures, and — relevant here — why the assembly carries a complete, readable description of your program. A decompiler reconstructs C# from that IL and metadata; an IL viewer just shows you the IL directly, with no reconstruction step in between. There is more background in how to decompile a .NET DLL to C#.
How to view IL
You have a few options, from friendliest to most bare-metal:
- A decompiler with an IL view. The most convenient path. Open the assembly, select a method, and switch that member from its C# view to its IL view. In Glass.NET this is a per-member toggle, so you can read the reconstructed C# and the underlying IL of the same method side by side without leaving the tool. No source or PDB required.
ildasm.exe. The IL Disassembler ships with the .NET SDK / Windows SDK. It is the classic, no-frills way to dump an assembly’s IL and metadata to a window or a text file. Great for scripting and for a canonical dump.monodis/ other disassemblers. Cross-platform alternatives exist if you are not on the Microsoft toolchain.
For interactive work — jump to a method, read its C#, then confirm against its IL — a decompiler view is by far the fastest, because you are one keystroke from the C# you were already reading.
When reading IL beats reading C#
Decompiled C# is easier to read, so why ever drop to IL? Because the C# is a reconstruction, and there are cases where the reconstruction hides or reshapes something you need to see:
- The C# looks surprising. A decompiler’s pattern-matching occasionally produces valid C# that reads oddly. The IL tells you what the method literally does, so you can confirm the behavior instead of second-guessing the decompiler.
- Hidden behavior. Boxing, implicit conversions,
stringconcatenation lowered toString.Concat, the exact target of a virtual vs non-virtual call — these are invisible or ambiguous in C# but explicit in IL. - Evaluation order and short-circuiting. When you need to know precisely what is evaluated and in what order, IL is unambiguous.
- The C# will not reconstruct cleanly. Heavily optimized, obfuscated, or unusual assemblies sometimes defeat clean decompilation. The IL is still fully readable even when the C# is not.
- Learning how the compiler works. If you want to understand how a language feature is lowered — how
yield,async,lock, or aswitchexpression become IL — reading the IL is the whole point.
For everything else — understanding logic, tracing a bug, recovering source — decompiled C# is the better tool. IL is the scalpel you reach for when the C# is not enough.
A small worked example
Take the most boring method imaginable:
public int Add(int a, int b)
{
return a + b;
}
In a release build with no locals, its IL is almost a one-to-one map of the stack machine:
.method public hidebysig instance int32 Add(int32 a, int32 b) cil managed
{
.maxstack 2
ldarg.1 // push a
ldarg.2 // push b
add // pop both, push a + b
ret // return the top of the stack
}
ldarg.1 and ldarg.2 push the two arguments (ldarg.0 is this on an instance method), add pops them and pushes the sum, and ret returns it. Nothing surprising — which is exactly the point: for simple code, IL just confirms what the C# says.
Now watch IL reveal something the C# hides. This line:
object o = 42;
reads as a trivial assignment. But object is a reference type and 42 is an int (a value type), so the runtime has to box it — allocate a heap object to hold the value. The C# does not show that; the IL does:
ldc.i4.s 42 // push the constant 42
box [System.Runtime]System.Int32 // box the int into an object
stloc.0 // store into local 'o'
That box instruction is a heap allocation you would never spot in the source. In a hot loop, spotting it in the IL is the difference between “why is this allocating?” and an actual answer. This is the everyday value of an IL view — it makes the invisible visible.
Honest limits
An IL view is a reading tool, not a magic decoder:
- IL is lower-level, so it is slower to read. A method that is three lines of C# can be a dozen IL instructions. Use it selectively.
- It is still static. Viewing IL does not run the code. If you need to watch values at runtime, that is a live debugger’s job — dnSpyEx for managed debugging.
- Metadata names still apply. Obfuscated assemblies have obfuscated names in the IL too; the instructions are readable, but the identifiers may not be meaningful.
- Only analyze what you have the right to. Reading the IL of your own binaries or dependencies you may inspect is routine; third-party software can be restricted by its license.
See the IL for yourself
If you have ever wanted to confirm what a method actually does — not what the decompiler thinks it does — an IL view is the answer. Glass.NET is a free .NET decompiler that shows you both: read the reconstructed C#, then flip any member to its IL in a keystroke. No license, no seats, no sign-up. Download it, open an assembly, and switch to the IL view. For the fuller reading workflow, see how to debug a .NET app when you don’t have the source.
Try Nebula.NET
Harden your .NET code in minutes — start with the free edition.