What can you do in MSIL that you cannot do in C or VB.NET?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
MSIL, more commonly called CIL or just IL, does not give you magical powers beyond what the CLR can execute. What it does give you is lower-level control over CLR behavior than C# or VB.NET usually expose directly. So the real answer is: IL lets you express certain runtime features, calling conventions, and unverifiable behaviors that high-level .NET languages intentionally restrict or hide.
The Big Idea: IL Is Closer to the CLR
C# and VB.NET are language designs. They choose what syntax to support, what safety guarantees to enforce, and which CLR features to expose. IL is much closer to the runtime instruction set, so it can represent details that the source languages leave out.
That means some things are possible in IL because:
- the CLR supports them
- the source language designers chose not to expose them directly
- or the construct is considered too unsafe or too obscure for normal application code
Example 1: Emit Unverifiable or Unsafe Patterns
IL can describe code that the CLR may still load but that C# or VB.NET will not let you express directly in normal safe code.
For example, IL gives you direct stack-machine instructions:
That example is simple and safe, but IL also gives you access to patterns that can break type-safety guarantees more easily than C# or VB.NET normally allow.
This is one reason tools such as ilasm and ildasm are powerful but also easy to misuse.
Example 2: Use IL Prefixes and Runtime Hints
IL supports low-level instruction prefixes such as tail., volatile., unaligned., and constrained.. Some of these are only partially reachable from C# and VB.NET, and some are not directly under your control at all.
A tail-call example in IL looks like this:
High-level languages may emit a tail call in some cases, but IL gives you direct control over the instruction prefix rather than leaving the choice entirely to the compiler.
Example 3: Define Managed Varargs
The CLR supports managed vararg methods, but C# does not let you define arbitrary managed vararg methods in the normal language the way IL can.
An IL signature can express this directly:
C# has params, but that is not the same thing. params is just an array convenience at the language level. Managed varargs are a different runtime feature.
Example 4: Finer Control Over Metadata and Signatures
IL can express metadata details that C# and VB.NET often hide, including certain custom modifiers, explicit calling conventions, and unusual signature shapes. That matters mostly to compiler writers, interop authors, and tooling developers rather than ordinary app code.
For example, when generating or rewriting assemblies, IL tools can produce method signatures that no normal C# file could have authored directly.
This is one reason post-compilation tools exist at all. They can manipulate assemblies in ways the original source language never exposed.
Example 5: Work at the Stack Level
IL is a stack-based language. You control pushes, pops, loads, stores, and branching much more explicitly than in C# or VB.NET.
A C# compiler turns source statements into this kind of stack manipulation behind the scenes, but in IL you are responsible for getting the stack behavior right yourself.
That makes IL more flexible, but also much easier to break.
What IL Does Not Change
It is important not to overstate the difference. IL does not let you escape the CLR entirely. If the runtime forbids something, IL cannot simply wish it into existence.
So the practical distinction is not "IL can do anything." It is "IL can express some CLR features more directly, and it can bypass some source-language restrictions."
Why Most Developers Still Use C# or VB.NET
For ordinary application development, the extra power is usually not worth the loss of safety, tooling, and readability. C# and VB.NET protect you from:
- stack-balance mistakes
- many type-safety violations
- unreadable metadata-level tricks
- awkward runtime-specific edge cases
That is a feature, not a limitation, for most teams.
IL becomes valuable when you are:
- writing compilers
- inspecting assemblies
- building code generators
- doing post-build rewriting
- studying how the CLR really behaves
Common Pitfalls
One common mistake is assuming IL is "better C#." It is not. It is a lower-level representation with different tradeoffs.
Another issue is confusing params in C# with managed varargs in IL. They solve different problems at different layers.
Developers also sometimes assume any valid IL is safe. Some IL is unverifiable or fragile even if the runtime accepts it.
Finally, manually written IL is very hard to maintain. Even when IL can express something that C# cannot, that does not mean it is the right implementation strategy for production code.
Summary
- IL can express some CLR features that C# and VB.NET do not expose directly.
- Examples include certain unverifiable patterns, IL prefixes, managed varargs, and metadata-level signature details.
- IL gives finer control over stack operations and runtime instructions.
- This is mainly useful for tooling, compilers, and low-level runtime work.
- For normal application code, C# and VB.NET are usually preferable because they trade some flexibility for safety and clarity.

