Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Assemblies, metadata, and IL

โญโญโญ advancedโณ 15 min read๐Ÿ“ Lesson 222 of 265

What ships in a DLL, how IL encodes your intent, and how the loader resolves references.

Assemblies, metadata, and IL

A .NET assembly is a Windows PE file (even on Linux โ€” same container format) with three payloads:

  1. Metadata tables โ€” every type, method, field, attribute, and their signatures, in a binary table format. This is what reflection reads and what the compiler consumed when referencing you. Full fidelity: C# features are metadata, not syntax.
  2. IL โ€” the stack-based bytecode per method body. Stack-based: ldc.i4.5; ldc.i4.3; add pushes 5, pushes 3, adds. Verified at load time (type safety of the stack is checkable).
  3. Resources / manifest โ€” embedded data and the assembly version/ culture/strong-name identity.

The loader resolves AssemblyRef entries through the hosting runtime: app-local first, then the framework, then AssemblyLoadContext rules. The classic production failure โ€” FileLoadException or "could not load type" โ€” is a resolver story: two versions of one identity, or a dependency present in dev but trimmed from publish.

IL inspection without ildasm: reflection gives you the method body's local signatures, and MethodBody.GetILAsByteArray() gives raw IL bytes โ€” enough to see that async, closures, and lock all rewrite your code into other patterns. The decompiler-grade view (ILSpy) is a desktop tool, but everything it shows is derived from exactly these metadata tables โ€” nothing magic, all documented.

What this buys you in practice: reading an exception's stack trace means reading IL-to-source correspondence; understanding trimming means knowing what metadata the linker can prove unused; and diagnosting versioning means inspecting the manifest, not guessing.